@rsaglobaltech/specgate 0.26.0 → 0.27.2
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/CHANGELOG.md +47 -0
- package/README.md +6 -0
- package/dist/packages/core/src/domain/ChangeMeasures.js +198 -0
- package/dist/packages/core/src/domain/ChangeMeasures.js.map +1 -0
- package/dist/packages/core/src/domain/ConventionalCommit.js +73 -0
- package/dist/packages/core/src/domain/ConventionalCommit.js.map +1 -0
- package/dist/packages/core/src/domain/HarnessConfig.js.map +1 -1
- package/dist/packages/core/src/domain/HarnessReport.js +2 -0
- package/dist/packages/core/src/domain/HarnessReport.js.map +1 -1
- package/dist/packages/core/src/domain/QualityChecks.js +241 -0
- package/dist/packages/core/src/domain/QualityChecks.js.map +1 -0
- package/dist/packages/core/src/domain/QualitySummary.js +58 -0
- package/dist/packages/core/src/domain/QualitySummary.js.map +1 -0
- package/dist/packages/core/src/domain/Sarif.js +121 -0
- package/dist/packages/core/src/domain/Sarif.js.map +1 -0
- package/dist/packages/core/src/infrastructure/GitContributionStager.js +1 -1
- package/dist/packages/core/src/infrastructure/GitContributionStager.js.map +1 -1
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js +21 -0
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js.map +1 -1
- package/dist/packages/core/src/infrastructure/QualityBaselineFile.js +67 -0
- package/dist/packages/core/src/infrastructure/QualityBaselineFile.js.map +1 -0
- package/dist/scripts/cli/commands/change/TicketCommand.js +1 -1
- package/dist/scripts/cli/commands/change/TicketCommand.js.map +1 -1
- package/dist/scripts/cli/commands/harness/RunCommand.js +212 -15
- package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/Forja.js +61 -5
- package/dist/scripts/cli/commands/project/Forja.js.map +1 -1
- package/dist/scripts/cli/commands/project/InitProjectCommand.js +7 -0
- package/dist/scripts/cli/commands/project/InitProjectCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/CheckCommand.js +54 -3
- package/dist/scripts/cli/commands/quality/CheckCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/QualityCommand.js +336 -0
- package/dist/scripts/cli/commands/quality/QualityCommand.js.map +1 -0
- package/dist/scripts/cli/commands/spec/DoneCommand.js +4 -1
- package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/StudioCommand.js +14 -0
- package/dist/scripts/cli/commands/spec/StudioCommand.js.map +1 -1
- package/dist/scripts/docs/nav.js +13 -2
- package/dist/scripts/docs/nav.js.map +1 -1
- package/dist/scripts/harness/prompt.js +4 -0
- package/dist/scripts/harness/prompt.js.map +1 -1
- package/dist/scripts/harness/report.js +13 -0
- package/dist/scripts/harness/report.js.map +1 -1
- package/dist/scripts/lib/quality-presets.js +458 -0
- package/dist/scripts/lib/quality-presets.js.map +1 -0
- package/dist/scripts/lib/quality-run.js +595 -0
- package/dist/scripts/lib/quality-run.js.map +1 -0
- package/dist/scripts/lib/sonar-local.js +308 -0
- package/dist/scripts/lib/sonar-local.js.map +1 -0
- package/dist/scripts/lib/studio-app-page.js +74 -11
- package/dist/scripts/lib/studio-app-page.js.map +1 -1
- package/dist/scripts/lib/studio-app.js +80 -0
- package/dist/scripts/lib/studio-app.js.map +1 -1
- package/dist/scripts/lib/studio-route.js +64 -1
- package/dist/scripts/lib/studio-route.js.map +1 -1
- package/dist/scripts/lib/surface.js +11 -0
- package/dist/scripts/lib/surface.js.map +1 -1
- package/dist/scripts/lib/ticket-keys.js +1 -1
- package/dist/scripts/lib/ticket-keys.js.map +1 -1
- package/dist/scripts/quality.js +10 -0
- package/dist/scripts/quality.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SONAR_SARIF = exports.SONAR = void 0;
|
|
37
|
+
exports.ensureServer = ensureServer;
|
|
38
|
+
exports.analysisToken = analysisToken;
|
|
39
|
+
exports.projectKey = projectKey;
|
|
40
|
+
exports.scannerProperties = scannerProperties;
|
|
41
|
+
exports.issuesToSarif = issuesToSarif;
|
|
42
|
+
exports.runSonar = runSonar;
|
|
43
|
+
const fs = __importStar(require("node:fs"));
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const node_child_process_1 = require("node:child_process");
|
|
46
|
+
/**
|
|
47
|
+
* SonarQube Community, run locally in Docker, as one more check (REQ-226):
|
|
48
|
+
* no account and no server to host. The first run starts the server in a
|
|
49
|
+
* named container on a private network, bound to 127.0.0.1 only; a token for
|
|
50
|
+
* analysis is created once with the server's own default login and kept in
|
|
51
|
+
* `.specgate/sonar.local`, git-ignored — the person's admin password is never
|
|
52
|
+
* changed or stored, so they log in with admin/admin and choose their own.
|
|
53
|
+
* Each run analyses the project with the scanner image and writes the open
|
|
54
|
+
* issues as SARIF, so the baseline judges them like any other check's.
|
|
55
|
+
*/
|
|
56
|
+
exports.SONAR = {
|
|
57
|
+
image: "sonarqube:25.9.0.112764-community",
|
|
58
|
+
scanner: "sonarsource/sonar-scanner-cli:11.4",
|
|
59
|
+
container: "specgate-sonarqube",
|
|
60
|
+
network: "specgate-sonar",
|
|
61
|
+
port: 9123,
|
|
62
|
+
};
|
|
63
|
+
const BASE = `http://127.0.0.1:${exports.SONAR.port}`;
|
|
64
|
+
const LOCAL = path.join(".specgate", "sonar.local");
|
|
65
|
+
exports.SONAR_SARIF = ".specgate/quality/sonar.sarif";
|
|
66
|
+
const docker = (args, timeout = 120000) => (0, node_child_process_1.spawnSync)("docker", args, { encoding: "utf8", timeout });
|
|
67
|
+
/**
|
|
68
|
+
* One HTTP call, synchronously: the checks run in spawnSync's world, so a
|
|
69
|
+
* short Node child does the fetch and prints the JSON.
|
|
70
|
+
*/
|
|
71
|
+
function http(method, url, auth, form) {
|
|
72
|
+
const script = [
|
|
73
|
+
"(async () => {",
|
|
74
|
+
" try {",
|
|
75
|
+
" const [method, url, auth, form] = JSON.parse(process.argv[1]);",
|
|
76
|
+
" const r = await fetch(url, {",
|
|
77
|
+
" method,",
|
|
78
|
+
" headers: Object.assign(auth ? { Authorization: 'Basic ' + Buffer.from(auth).toString('base64') } : {},",
|
|
79
|
+
" form ? { 'content-type': 'application/x-www-form-urlencoded' } : {}),",
|
|
80
|
+
" body: form ? new URLSearchParams(form).toString() : undefined,",
|
|
81
|
+
" });",
|
|
82
|
+
" const text = await r.text();",
|
|
83
|
+
" let body = null; try { body = JSON.parse(text); } catch { body = text; }",
|
|
84
|
+
" process.stdout.write(JSON.stringify({ status: r.status, body }));",
|
|
85
|
+
" } catch (e) { process.stdout.write(JSON.stringify({ status: 0, body: String(e) })); }",
|
|
86
|
+
"})();",
|
|
87
|
+
].join("\n");
|
|
88
|
+
const r = (0, node_child_process_1.spawnSync)(process.execPath, ["-e", script, JSON.stringify([method, url, auth, form || null])], {
|
|
89
|
+
encoding: "utf8",
|
|
90
|
+
timeout: 60000,
|
|
91
|
+
});
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(r.stdout || "");
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return { status: 0, body: r.stderr || "" };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const sleep = (ms) => (0, node_child_process_1.spawnSync)(process.execPath, ["-e", `setTimeout(() => {}, ${ms})`]);
|
|
100
|
+
/** The server, started if it is not, and answering UP. */
|
|
101
|
+
function ensureServer(timeoutMs = 5 * 60 * 1000) {
|
|
102
|
+
docker(["network", "create", exports.SONAR.network]);
|
|
103
|
+
const state = (docker(["inspect", "-f", "{{.State.Running}}", exports.SONAR.container]).stdout || "").trim();
|
|
104
|
+
if (state === "false")
|
|
105
|
+
docker(["start", exports.SONAR.container]);
|
|
106
|
+
else if (state !== "true") {
|
|
107
|
+
const run = docker([
|
|
108
|
+
"run",
|
|
109
|
+
"-d",
|
|
110
|
+
"--name",
|
|
111
|
+
exports.SONAR.container,
|
|
112
|
+
"--network",
|
|
113
|
+
exports.SONAR.network,
|
|
114
|
+
"-p",
|
|
115
|
+
`127.0.0.1:${exports.SONAR.port}:9000`,
|
|
116
|
+
// Its Elasticsearch asks the host for vm.max_map_count; Docker Desktop
|
|
117
|
+
// does not let a container set it, and one local instance does not need it.
|
|
118
|
+
"-e",
|
|
119
|
+
"SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true",
|
|
120
|
+
"-v",
|
|
121
|
+
"specgate-sonar-data:/opt/sonarqube/data",
|
|
122
|
+
"-v",
|
|
123
|
+
"specgate-sonar-extensions:/opt/sonarqube/extensions",
|
|
124
|
+
exports.SONAR.image,
|
|
125
|
+
], 15 * 60 * 1000);
|
|
126
|
+
if (run.status !== 0) {
|
|
127
|
+
return { ok: false, message: `could not start SonarQube: ${(run.stderr || "").trim()}` };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const until = Date.now() + timeoutMs;
|
|
131
|
+
while (Date.now() < until) {
|
|
132
|
+
const r = http("GET", `${BASE}/api/system/status`, "");
|
|
133
|
+
if (r.status === 200 && r.body.status === "UP")
|
|
134
|
+
return { ok: true };
|
|
135
|
+
sleep(3000);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
message: `SonarQube did not come up at ${BASE} within ${timeoutMs / 60000} min`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** The analysis token: SONAR_TOKEN, else the one kept, else one created with the default login. */
|
|
143
|
+
function analysisToken(projectDir) {
|
|
144
|
+
if (process.env.SONAR_TOKEN)
|
|
145
|
+
return { ok: true, token: process.env.SONAR_TOKEN };
|
|
146
|
+
const file = path.join(projectDir, LOCAL);
|
|
147
|
+
try {
|
|
148
|
+
const kept = JSON.parse(fs.readFileSync(file, "utf8")).token;
|
|
149
|
+
const checked = kept ? http("GET", `${BASE}/api/authentication/validate`, `${kept}:`) : null;
|
|
150
|
+
if (checked && checked.status === 200 && checked.body.valid) {
|
|
151
|
+
return { ok: true, token: kept };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
/* none kept yet */
|
|
156
|
+
}
|
|
157
|
+
const made = http("POST", `${BASE}/api/user_tokens/generate`, "admin:admin", {
|
|
158
|
+
name: `specgate-${path.basename(projectDir)}-${Date.now()}`,
|
|
159
|
+
type: "GLOBAL_ANALYSIS_TOKEN",
|
|
160
|
+
});
|
|
161
|
+
const token = made.body.token;
|
|
162
|
+
if (made.status !== 200 || !token) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
message: "SonarQube's default login no longer works (its password was changed, as it should be). " +
|
|
166
|
+
`Create a token at ${BASE}/account/security and set it as SONAR_TOKEN in the environment.`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
170
|
+
fs.writeFileSync(file, JSON.stringify({ token }, null, 2) + "\n", "utf8");
|
|
171
|
+
ensureIgnored(projectDir, ".specgate/sonar.local");
|
|
172
|
+
return { ok: true, token };
|
|
173
|
+
}
|
|
174
|
+
/** A line in the project's .gitignore, added once. */
|
|
175
|
+
function ensureIgnored(projectDir, entry) {
|
|
176
|
+
const file = path.join(projectDir, ".gitignore");
|
|
177
|
+
const text = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
178
|
+
if (!text.split(/\r?\n/).includes(entry)) {
|
|
179
|
+
fs.writeFileSync(file, `${text.replace(/\n?$/, "\n")}${entry}\n`, "utf8");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** The project key: the folder's name, as SonarQube accepts it. */
|
|
183
|
+
function projectKey(projectDir) {
|
|
184
|
+
return (path
|
|
185
|
+
.basename(path.resolve(projectDir))
|
|
186
|
+
.replace(/[^A-Za-z0-9_.:-]+/g, "-")
|
|
187
|
+
.replace(/^-|-$/g, "") || "project");
|
|
188
|
+
}
|
|
189
|
+
/** The scanner's properties for this project: Java sources and classes when it has them. */
|
|
190
|
+
function scannerProperties(projectDir, key) {
|
|
191
|
+
const has = (rel) => fs.existsSync(path.join(projectDir, rel));
|
|
192
|
+
const props = [
|
|
193
|
+
`-Dsonar.projectKey=${key}`,
|
|
194
|
+
"-Dsonar.scm.disabled=true",
|
|
195
|
+
"-Dsonar.working.directory=.specgate/quality/sonar",
|
|
196
|
+
];
|
|
197
|
+
if (has("src/main/java")) {
|
|
198
|
+
props.push("-Dsonar.sources=src/main/java");
|
|
199
|
+
if (has("src/test/java"))
|
|
200
|
+
props.push("-Dsonar.tests=src/test/java");
|
|
201
|
+
props.push(`-Dsonar.java.binaries=${has("target/classes") ? "target/classes" : has("build/classes") ? "build/classes" : "."}`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
props.push("-Dsonar.sources=.", "-Dsonar.exclusions=**/node_modules/**,**/dist/**,**/build/**,**/coverage/**,**/.specgate/**,**/.harness/**");
|
|
205
|
+
}
|
|
206
|
+
return props;
|
|
207
|
+
}
|
|
208
|
+
/** SonarQube's open issues as SARIF 2.1.0, its line hash as the fingerprint. */
|
|
209
|
+
function issuesToSarif(issues, key) {
|
|
210
|
+
const level = (s) => s === "BLOCKER" || s === "CRITICAL" ? "error" : s === "MAJOR" ? "warning" : "note";
|
|
211
|
+
return JSON.stringify({
|
|
212
|
+
version: "2.1.0",
|
|
213
|
+
runs: [
|
|
214
|
+
{
|
|
215
|
+
tool: { driver: { name: "SonarQube Community" } },
|
|
216
|
+
results: issues.map((i) => ({
|
|
217
|
+
ruleId: i.rule,
|
|
218
|
+
level: level(i.severity),
|
|
219
|
+
message: { text: i.message },
|
|
220
|
+
locations: [
|
|
221
|
+
{
|
|
222
|
+
physicalLocation: {
|
|
223
|
+
artifactLocation: { uri: i.component.replace(new RegExp(`^${key}:`), "") },
|
|
224
|
+
region: { startLine: i.line || 1 },
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
...(i.hash ? { partialFingerprints: { sonarLineHash: i.hash } } : {}),
|
|
229
|
+
})),
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
}, null, 2);
|
|
233
|
+
}
|
|
234
|
+
/** Analyse the project and write its open issues as SARIF; shaped like spawnSync's result. */
|
|
235
|
+
function runSonar(projectDir, timeoutMs) {
|
|
236
|
+
const fail = (message) => ({ status: 1, stdout: "", stderr: message });
|
|
237
|
+
const up = ensureServer();
|
|
238
|
+
if (!up.ok)
|
|
239
|
+
return fail(up.message);
|
|
240
|
+
const tok = analysisToken(projectDir);
|
|
241
|
+
if (!tok.ok)
|
|
242
|
+
return fail(tok.message);
|
|
243
|
+
const key = projectKey(projectDir);
|
|
244
|
+
const posix = process.platform !== "win32" && typeof process.getuid === "function";
|
|
245
|
+
const scan = (0, node_child_process_1.spawnSync)("docker", [
|
|
246
|
+
"run",
|
|
247
|
+
"--rm",
|
|
248
|
+
"--network",
|
|
249
|
+
exports.SONAR.network,
|
|
250
|
+
"-v",
|
|
251
|
+
`${path.resolve(projectDir)}:/usr/src`,
|
|
252
|
+
"-w",
|
|
253
|
+
"/usr/src",
|
|
254
|
+
...(posix ? ["--user", `${process.getuid()}:${process.getgid ? process.getgid() : 0}`] : []),
|
|
255
|
+
"-e",
|
|
256
|
+
"SONAR_TOKEN",
|
|
257
|
+
"-e",
|
|
258
|
+
`SONAR_HOST_URL=http://${exports.SONAR.container}:9000`,
|
|
259
|
+
"-e",
|
|
260
|
+
"SONAR_USER_HOME=/tmp/.sonar",
|
|
261
|
+
exports.SONAR.scanner,
|
|
262
|
+
...scannerProperties(projectDir, key),
|
|
263
|
+
], {
|
|
264
|
+
encoding: "utf8",
|
|
265
|
+
timeout: timeoutMs,
|
|
266
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
267
|
+
env: { ...process.env, SONAR_TOKEN: tok.token },
|
|
268
|
+
});
|
|
269
|
+
if (scan.status !== 0) {
|
|
270
|
+
return {
|
|
271
|
+
status: scan.status,
|
|
272
|
+
stdout: scan.stdout || "",
|
|
273
|
+
stderr: scan.stderr || "",
|
|
274
|
+
error: scan.error,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
// The analysis is processed on the server after the scanner ends.
|
|
278
|
+
const report = path.join(projectDir, ".specgate", "quality", "sonar", "report-task.txt");
|
|
279
|
+
const task = (/ceTaskId=(\S+)/.exec(fs.existsSync(report) ? fs.readFileSync(report, "utf8") : "") || [])[1];
|
|
280
|
+
if (task) {
|
|
281
|
+
for (let i = 0; i < 120; i += 1) {
|
|
282
|
+
const t = http("GET", `${BASE}/api/ce/task?id=${task}`, `${tok.token}:`).body;
|
|
283
|
+
const status = t && t.task ? t.task.status : "";
|
|
284
|
+
if (status && status !== "PENDING" && status !== "IN_PROGRESS")
|
|
285
|
+
break;
|
|
286
|
+
sleep(2000);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const issues = [];
|
|
290
|
+
for (let page = 1; page <= 20; page += 1) {
|
|
291
|
+
const r = http("GET", `${BASE}/api/issues/search?componentKeys=${encodeURIComponent(key)}&resolved=false&ps=500&p=${page}`, `${tok.token}:`);
|
|
292
|
+
const body = r.body;
|
|
293
|
+
if (r.status !== 200 || !body.issues)
|
|
294
|
+
return fail(`could not read the issues: HTTP ${r.status}`);
|
|
295
|
+
issues.push(...body.issues);
|
|
296
|
+
if (issues.length >= (body.total || 0) || body.issues.length === 0)
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
const out = path.join(projectDir, exports.SONAR_SARIF);
|
|
300
|
+
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
301
|
+
fs.writeFileSync(out, issuesToSarif(issues, key), "utf8");
|
|
302
|
+
return {
|
|
303
|
+
status: 0,
|
|
304
|
+
stdout: `SonarQube analysed ${key}: ${issues.length} open issue(s). ${BASE}/dashboard?id=${encodeURIComponent(key)}`,
|
|
305
|
+
stderr: "",
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=sonar-local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sonar-local.js","sourceRoot":"","sources":["../../../scripts/lib/sonar-local.ts"],"names":[],"mappings":"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"}
|
|
@@ -128,7 +128,7 @@ function studioAppPage(projectName, token = "") {
|
|
|
128
128
|
home:"Inicio", questions:"Preguntas", review:"Revisión", build:"Construir", simple:"Simple", adv:"Avanzado",
|
|
129
129
|
routeTitle:"Tu proyecto, paso a paso", routeIntro:"Del brief a la app construida y probada. Cada paso se abre cuando el anterior está listo; puedes volver a cualquiera.",
|
|
130
130
|
next:"Lo siguiente", stepN:"paso", open:"Abrir", allDone:"Todo hecho", allDoneP:"Todo está construido y probado. Añade algo al brief cuando quieras crecer la app.",
|
|
131
|
-
nx_project:"Crea tu proyecto", nxp_project:"
|
|
131
|
+
nx_project:"Crea tu proyecto", nxp_project:"Cinco preguntas y specgate prepara esta carpeta.", go_project:"Empezar",
|
|
132
132
|
nx_brief:"Escribe el brief", nxp_brief:"Qué tiene que hacer la app, con tus palabras. De aquí salen los requisitos.", go_brief:"Abrir el brief",
|
|
133
133
|
nx_drafts:"Redacta los borradores", nxp_drafts:"El asistente convierte cada módulo del brief en requisitos con ejemplos.", go_drafts:"Redactar",
|
|
134
134
|
nx_questions:"Responde las preguntas", nxp_questions:"El asistente o un borrador espera una decisión tuya.", go_questions:"Responder",
|
|
@@ -143,7 +143,7 @@ function studioAppPage(projectName, token = "") {
|
|
|
143
143
|
s_report:"B de T construidos y probados", s_report_locked:"Aparece con el primer requisito construido", s_locked:"Se abre con el paso anterior",
|
|
144
144
|
repeat:"Los pasos 3 a 6 se repiten por módulo: puedes construir uno mientras redactas otro.",
|
|
145
145
|
loading:"Cargando…", mods:"módulos", sOf:"de", modules:"Módulos", ready:"listo", qWait:"pregunta", draft:"borrador por revisar", reqs:"requisitos",
|
|
146
|
-
newTitle:"Empecemos un proyecto", newIntro:"Esta carpeta está vacía.
|
|
146
|
+
newTitle:"Empecemos un proyecto", newIntro:"Esta carpeta está vacía. Cinco preguntas y specgate la prepara: especificación, pruebas y el asistente que programa.",
|
|
147
147
|
q1:"1 · ¿Cómo se llama?", q2:"2 · ¿Qué vas a construir?", q3:"3 · ¿Con qué tecnología?", q4:"4 · ¿Qué asistente programa?",
|
|
148
148
|
kWeb:"Una web", kWebP:"Pantallas que se abren en el navegador", kApi:"Un servicio (API)", kApiP:"Datos y reglas para otras apps", kMob:"Una app móvil", kMobP:"Para el teléfono",
|
|
149
149
|
stNode:"Node.js + React (recomendado para una web)", stJava:"Java + Spring Boot", stPy:"Python + FastAPI", stCustom:"Otra… (la escribes tú)",
|
|
@@ -180,17 +180,25 @@ function studioAppPage(projectName, token = "") {
|
|
|
180
180
|
whoCodes:"¿Quién programa?", whoCodesP:"Los asistentes de este proyecto. Se recuerda tu elección.", noEstimate:"Sin estimación todavía", perRun:"por requisito",
|
|
181
181
|
readyN:"Listos para construir", minutes:"min aprox.", perReq:"por requisito", buildN:"Construir los N", build1:"Construir este", noAgent:"Falta configurar el asistente (agent en harness.config.yaml).",
|
|
182
182
|
running:"En marcha", stP:"Programa", stT:"Prueba", stQ:"Revisa la calidad", stS:"Guarda", watch:"Ver en directo", stop:"Detener después de este", stopAsked:"Se detendrá al terminar este.",
|
|
183
|
-
doneToday:"Terminados", nothingReady:"No hay nada listo para construir.", started:"En marcha: ", saved:"Guardado.", err:"No se pudo: ",
|
|
183
|
+
doneToday:"Terminados", nothingReady:"No hay nada listo para construir.", started:"En marcha: ", savedGit:"la especificación quedó guardada en git (N archivos)", saved:"Guardado.", err:"No se pudo: ",
|
|
184
184
|
repTitle:"Lo que está hecho", repIntro:"Cada requisito se construyó y se probó con el ejemplo que lo describe. Este informe sale de la especificación y de las pruebas.",
|
|
185
185
|
download:"Descargar informe", print:"Imprimir / PDF", rBuilt:"Construido", rExamples:"Ejemplos probados", rGate:"Calidad", rGateOk:"Pasa", rGateBad:"No pasa", rCost:"Coste del asistente",
|
|
186
186
|
byModule:"Por módulo", colModule:"Módulo", colBuilt:"Hecho", colEx:"Ejemplos", colState:"Estado", complete:"Completo", waitingN:"N esperan una respuesta", notBuilt:"Sin construir",
|
|
187
187
|
whatDoes:"Qué hace ya la app", provedBy:"Probado con: ", missing:"Lo que falta", missingNone:"Nada.", waitsFor:"espera la respuesta a: ", decisions:"Decisiones y supuestos",
|
|
188
|
-
decisionsP:"Q preguntas respondidas · A supuestos anotados.", nothingBuilt:"Todavía no hay nada construido."
|
|
188
|
+
decisionsP:"Q preguntas respondidas · A supuestos anotados.", nothingBuilt:"Todavía no hay nada construido.",
|
|
189
|
+
qualTitle:"Calidad del código", qualIntro:"Comprobaciones que el asistente debe pasar en cada requisito, todas en Docker y sin cuentas.",
|
|
190
|
+
qualNone:"Sin comprobaciones de calidad todavía.", qualBasic:"Activar el nivel básico", qualStrict:"Activar el nivel estricto",
|
|
191
|
+
toBasic:"Cambiar a básico", toStrict:"Cambiar a estricto", gates:"bloquea", advises:"avisa", notRunnable:"no se puede ejecutar aquí",
|
|
192
|
+
dockerOn:"Docker responde", dockerOff:"Docker no responde: arranca Docker Desktop", thresholds:"Umbrales: cobertura de lo cambiado C % · mutación M %",
|
|
193
|
+
stAttempt:"en cada intento", stEnd:"al final", stCi:"en CI",
|
|
194
|
+
q5:"5 · ¿Qué nivel de calidad?", qlBasic:"Básico (recomendado)", qlBasicP:"Secretos, dependencias, tests que cubren lo cambiado y análisis estático. Todo en Docker.",
|
|
195
|
+
qlStrict:"Estricto", qlStrictP:"Además: tests de mutación, complejidad y SAST. Más lento, más exigente.", qlNone:"Ninguno por ahora", qlNoneP:"Se puede activar después en Proyecto.",
|
|
196
|
+
rQuality:"Calidad del código", rQualityP:"cobertura C % · mutación M %", covShort:"cobertura", mutShort:"mutación"},
|
|
189
197
|
en: {route:"Route", project:"Project", brief:"Brief", drafts:"Drafts", report:"Report",
|
|
190
198
|
home:"Home", questions:"Questions", review:"Review", build:"Build", simple:"Simple", adv:"Advanced",
|
|
191
199
|
routeTitle:"Your project, step by step", routeIntro:"From the brief to the app built and proven. Each step opens once the one before is ready; you can go back to any.",
|
|
192
200
|
next:"Next", stepN:"step", open:"Open", allDone:"All done", allDoneP:"Everything is built and proven. Add to the brief whenever you want the app to grow.",
|
|
193
|
-
nx_project:"Create your project", nxp_project:"
|
|
201
|
+
nx_project:"Create your project", nxp_project:"Five questions and specgate prepares this folder.", go_project:"Start",
|
|
194
202
|
nx_brief:"Write the brief", nxp_brief:"What the app has to do, in your words. The requirements come from it.", go_brief:"Open the brief",
|
|
195
203
|
nx_drafts:"Draft the modules", nxp_drafts:"The assistant turns each module of the brief into requirements with examples.", go_drafts:"Draft",
|
|
196
204
|
nx_questions:"Answer the questions", nxp_questions:"The assistant or a draft is waiting for your decision.", go_questions:"Answer",
|
|
@@ -205,7 +213,7 @@ function studioAppPage(projectName, token = "") {
|
|
|
205
213
|
s_report:"B of T built and proven", s_report_locked:"Appears with the first requirement built", s_locked:"Opens with the step before",
|
|
206
214
|
repeat:"Steps 3 to 6 repeat per module: you can build one while another is drafted.",
|
|
207
215
|
loading:"Loading…", mods:"modules", sOf:"of", modules:"Modules", ready:"done", qWait:"question", draft:"draft to review", reqs:"requirements",
|
|
208
|
-
newTitle:"Let's start a project", newIntro:"This folder is empty.
|
|
216
|
+
newTitle:"Let's start a project", newIntro:"This folder is empty. Five questions and specgate prepares it: specification, tests and the assistant that codes.",
|
|
209
217
|
q1:"1 · What is it called?", q2:"2 · What are you building?", q3:"3 · With which technology?", q4:"4 · Which assistant codes?",
|
|
210
218
|
kWeb:"A website", kWebP:"Screens opened in the browser", kApi:"A service (API)", kApiP:"Data and rules for other apps", kMob:"A mobile app", kMobP:"For the phone",
|
|
211
219
|
stNode:"Node.js + React (recommended for a website)", stJava:"Java + Spring Boot", stPy:"Python + FastAPI", stCustom:"Other… (you write it)",
|
|
@@ -242,12 +250,20 @@ function studioAppPage(projectName, token = "") {
|
|
|
242
250
|
whoCodes:"Who codes?", whoCodesP:"The assistants of this project. Your choice is remembered.", noEstimate:"No estimate yet", perRun:"per requirement",
|
|
243
251
|
readyN:"Ready to build", minutes:"min approx.", perReq:"per requirement", buildN:"Build the N", build1:"Build this one", noAgent:"The assistant is not configured yet (agent in harness.config.yaml).",
|
|
244
252
|
running:"Running", stP:"Codes", stT:"Tests", stQ:"Checks quality", stS:"Saves", watch:"Watch live", stop:"Stop after this one", stopAsked:"It will stop once this one ends.",
|
|
245
|
-
doneToday:"Finished", nothingReady:"Nothing is ready to build.", started:"Started: ", saved:"Saved.", err:"Could not: ",
|
|
253
|
+
doneToday:"Finished", nothingReady:"Nothing is ready to build.", started:"Started: ", savedGit:"the specification was committed to git (N files)", saved:"Saved.", err:"Could not: ",
|
|
246
254
|
repTitle:"What is done", repIntro:"Each requirement was built and proven with the example that describes it. This report comes from the specification and the tests.",
|
|
247
255
|
download:"Download the report", print:"Print / PDF", rBuilt:"Built", rExamples:"Examples proven", rGate:"Quality", rGateOk:"Passes", rGateBad:"Fails", rCost:"Assistant cost",
|
|
248
256
|
byModule:"By module", colModule:"Module", colBuilt:"Built", colEx:"Examples", colState:"State", complete:"Complete", waitingN:"N wait for an answer", notBuilt:"Not built",
|
|
249
257
|
whatDoes:"What the app does", provedBy:"Proven with: ", missing:"What is missing", missingNone:"Nothing.", waitsFor:"waits for the answer to: ", decisions:"Decisions and assumptions",
|
|
250
|
-
decisionsP:"Q questions answered · A assumptions recorded.", nothingBuilt:"Nothing is built yet."
|
|
258
|
+
decisionsP:"Q questions answered · A assumptions recorded.", nothingBuilt:"Nothing is built yet.",
|
|
259
|
+
qualTitle:"Code quality", qualIntro:"Checks the assistant must pass on every requirement, all in Docker, no account needed.",
|
|
260
|
+
qualNone:"No quality checks yet.", qualBasic:"Turn on the basic level", qualStrict:"Turn on the strict level",
|
|
261
|
+
toBasic:"Switch to basic", toStrict:"Switch to strict", gates:"gates", advises:"advises", notRunnable:"cannot run here",
|
|
262
|
+
dockerOn:"Docker answers", dockerOff:"Docker does not answer: start Docker Desktop", thresholds:"Thresholds: diff coverage C % · mutation M %",
|
|
263
|
+
stAttempt:"every attempt", stEnd:"at the end", stCi:"in CI",
|
|
264
|
+
q5:"5 · Which quality level?", qlBasic:"Basic (recommended)", qlBasicP:"Secrets, dependencies, tests that cover the change and static analysis. All in Docker.",
|
|
265
|
+
qlStrict:"Strict", qlStrictP:"Also: mutation tests, complexity and SAST. Slower, stricter.", qlNone:"None for now", qlNoneP:"It can be turned on later in Project.",
|
|
266
|
+
rQuality:"Code quality", rQualityP:"coverage C % · mutation M %", covShort:"coverage", mutShort:"mutation"}
|
|
251
267
|
};
|
|
252
268
|
function load(k, d){ try { return localStorage.getItem(k) || d; } catch (e) { return d; } }
|
|
253
269
|
function save(k, v){ try { localStorage.setItem(k, v); } catch (e) {} }
|
|
@@ -366,7 +382,7 @@ function studioAppPage(projectName, token = "") {
|
|
|
366
382
|
}
|
|
367
383
|
|
|
368
384
|
// ── Project (REQ-213) ───────────────────────────────────────────────────
|
|
369
|
-
var np = {name:"", kind:"web", stack:"node-react", customStack:"", customTesting:"", agent:""};
|
|
385
|
+
var np = {name:"", kind:"web", stack:"node-react", customStack:"", customTesting:"", agent:"", quality:"basic"};
|
|
370
386
|
function viewProject(r, pr){
|
|
371
387
|
var m = $("view"); m.replaceChildren(); m.className = "narrow";
|
|
372
388
|
if (r.isProject) {
|
|
@@ -384,6 +400,7 @@ function studioAppPage(projectName, token = "") {
|
|
|
384
400
|
m.appendChild(c);
|
|
385
401
|
var hint = el("div", "card"); hint.append(el("span", "small muted", t("profilesHint")), el("code", "cmd", ["profiles_version: 1", "profiles:", " my-agent:", " agent: " + String.fromCharCode(34) + "my-agent < {prompt_file}" + String.fromCharCode(34)].join(String.fromCharCode(10))));
|
|
386
402
|
m.appendChild(hint);
|
|
403
|
+
if (cache.quality) m.appendChild(qualityCard(cache.quality));
|
|
387
404
|
return;
|
|
388
405
|
}
|
|
389
406
|
if (!r.empty) {
|
|
@@ -427,6 +444,14 @@ function studioAppPage(projectName, token = "") {
|
|
|
427
444
|
f4.appendChild(l);
|
|
428
445
|
});
|
|
429
446
|
f.appendChild(f4);
|
|
447
|
+
var fq = el("div", "field"); fq.appendChild(el("span", "lab", t("q5")));
|
|
448
|
+
var qch = el("div", "choices");
|
|
449
|
+
[["basic", "qlBasic", "qlBasicP"], ["strict", "qlStrict", "qlStrictP"], ["none", "qlNone", "qlNoneP"]].forEach(function(k){
|
|
450
|
+
var l = el("label", "choice" + (np.quality === k[0] ? " on" : "")), rr = el("input"); rr.type = "radio"; rr.name = "np-quality"; rr.checked = np.quality === k[0];
|
|
451
|
+
rr.addEventListener("change", function(){ np.quality = k[0]; render(); });
|
|
452
|
+
var h = el("span", "h"); h.append(rr, document.createTextNode(t(k[1]))); l.append(h, el("span", "small muted", t(k[2]))); qch.appendChild(l);
|
|
453
|
+
});
|
|
454
|
+
fq.appendChild(qch); f.appendChild(fq);
|
|
430
455
|
var f5 = el("div", "field"); flex(f5); f5.style.justifyContent = "space-between";
|
|
431
456
|
var m0 = msgEl();
|
|
432
457
|
var b = button("go", t("create"), function(){
|
|
@@ -441,6 +466,37 @@ function studioAppPage(projectName, token = "") {
|
|
|
441
466
|
m.append(f, m0, cmd("specgate init --new · specgate harness init · specgate propose --from-brief --new-brief"));
|
|
442
467
|
}
|
|
443
468
|
|
|
469
|
+
// ── Quality: the checks configured, how each runs here (REQ-229) ──────────
|
|
470
|
+
function qualityCard(q){
|
|
471
|
+
var c = el("section", "card");
|
|
472
|
+
c.append(el("h2", "", t("qualTitle")), el("span", "small muted", t("qualIntro")));
|
|
473
|
+
var dk = el("span", "small"); dk.textContent = q.docker ? t("dockerOn") : t("dockerOff"); dk.style.color = q.docker ? "var(--pass)" : "var(--fail)";
|
|
474
|
+
c.appendChild(dk);
|
|
475
|
+
if (!q.checks.length) c.appendChild(el("span", "muted", t("qualNone")));
|
|
476
|
+
else {
|
|
477
|
+
var level = el("div"); flex(level, 8);
|
|
478
|
+
if (q.level) level.appendChild(el("span", "pill b-info", q.level));
|
|
479
|
+
level.appendChild(el("span", "small muted", fill("thresholds", {C: q.diffCoverage === null ? "—" : q.diffCoverage, M: q.mutationScore === null ? "—" : q.mutationScore})));
|
|
480
|
+
c.appendChild(level);
|
|
481
|
+
var rows = el("div", "rows");
|
|
482
|
+
q.checks.forEach(function(k){
|
|
483
|
+
var l = el("div", "line"), g = el("div", "grow");
|
|
484
|
+
g.append(el("strong", "", k.name), el("span", "small muted", ({attempt:t("stAttempt"), end:t("stEnd"), ci:t("stCi")})[k.when] + " · " + (k.gate ? t("gates") : t("advises"))));
|
|
485
|
+
l.appendChild(g);
|
|
486
|
+
if (k.unavailable) { var u = el("span", "small", t("notRunnable")); u.style.color = "var(--fail)"; u.title = k.unavailable; l.appendChild(u); }
|
|
487
|
+
else l.appendChild(el("span", "pill b-soft", k.runner));
|
|
488
|
+
rows.appendChild(l);
|
|
489
|
+
});
|
|
490
|
+
c.appendChild(rows);
|
|
491
|
+
}
|
|
492
|
+
var acts = el("div"); flex(acts); var m0 = msgEl();
|
|
493
|
+
var level0 = q.checks.length ? q.level : "";
|
|
494
|
+
if (level0 !== "basic") acts.appendChild(button(level0 ? "ghost" : "go", level0 ? t("toBasic") : t("qualBasic"), function(){ post("api/quality/init", {level:"basic"}).then(function(r){ say(m0, r); if (r.ok) { cache.quality = r.view; render(); } }); }));
|
|
495
|
+
if (level0 !== "strict") acts.appendChild(button("ghost", level0 ? t("toStrict") : t("qualStrict"), function(){ post("api/quality/init", {level:"strict"}).then(function(r){ say(m0, r); if (r.ok) { cache.quality = r.view; render(); } }); }));
|
|
496
|
+
acts.appendChild(m0); c.append(acts, cmd("specgate quality init --level basic|strict · specgate quality doctor --pull · specgate quality baseline"));
|
|
497
|
+
return c;
|
|
498
|
+
}
|
|
499
|
+
|
|
444
500
|
// ── Profiles: who works, picked once for Brief, Drafts and Build (REQ-211) ──
|
|
445
501
|
function profileSelect(pr){
|
|
446
502
|
var wrap = el("label", "small muted"); flex(wrap, 6); wrap.appendChild(document.createTextNode(t("assistant")));
|
|
@@ -829,7 +885,7 @@ function studioAppPage(projectName, token = "") {
|
|
|
829
885
|
var label = function(){ var n = chosen().length; return n === 1 ? t("build1") : t("buildN").split("N").join(String(n)); };
|
|
830
886
|
var hasAgent = b.agent || !!who;
|
|
831
887
|
var go0 = button("go", label(), function(){
|
|
832
|
-
post("api/build", {reqs:chosen(), profile:who}).then(function(res){ m5.className = "msg " + (res.ok ? "ok" : "err"); m5.textContent = res.ok ? t("started") + res.started.join(", ") : t("err") + res.message; setTimeout(refresh, 1500); });
|
|
888
|
+
post("api/build", {reqs:chosen(), profile:who}).then(function(res){ m5.className = "msg " + (res.ok ? "ok" : "err"); m5.textContent = res.ok ? t("started") + res.started.join(", ") + (res.saved && res.saved.length ? " · " + fill("savedGit", {N:res.saved.length}) : "") : t("err") + res.message; setTimeout(refresh, 1500); });
|
|
833
889
|
});
|
|
834
890
|
go0.disabled = !b.ready.length || !hasAgent || !!b.running;
|
|
835
891
|
hd.append(info, go0); sec.appendChild(hd);
|
|
@@ -865,7 +921,7 @@ function studioAppPage(projectName, token = "") {
|
|
|
865
921
|
var dl = el("a", "go", t("download")); dl.href = "report.html"; dl.style.display = "inline-flex"; dl.style.alignItems = "center"; dl.style.color = "#fff";
|
|
866
922
|
acts.append(pdf, dl); top.append(g, acts); m.appendChild(top);
|
|
867
923
|
var s = el("section", "stats");
|
|
868
|
-
[[t("rBuilt"), r.built + " " + t("sOf") + " " + r.total, t("reqs"), ""], [t("rExamples"), String(r.scenarios), "", "var(--pass)"], [t("rGate"), r.gate.ok ? t("rGateOk") : t("rGateBad"), r.gate.findings[0] || "", r.gate.ok ? "var(--pass)" : "var(--fail)"], [t("rCost"), r.cost === null ? "—" : "$" + r.cost.toFixed(2), r.minutes + " min", ""]].forEach(function(x){
|
|
924
|
+
[[t("rBuilt"), r.built + " " + t("sOf") + " " + r.total, t("reqs"), ""], [t("rExamples"), String(r.scenarios), "", "var(--pass)"], [t("rGate"), r.gate.ok ? t("rGateOk") : t("rGateBad"), r.gate.findings[0] || "", r.gate.ok ? "var(--pass)" : "var(--fail)"], [t("rCost"), r.cost === null ? "—" : "$" + r.cost.toFixed(2), r.minutes + " min", ""]].concat(r.quality && r.quality.measured ? [[t("rQuality"), (r.quality.coverage === null ? "—" : r.quality.coverage + "%"), fill("rQualityP", {C: r.quality.coverage === null ? "—" : r.quality.coverage, M: r.quality.mutation === null ? "—" : r.quality.mutation}), "var(--pass)"]] : []).forEach(function(x){
|
|
869
925
|
var d = el("div", "stat"), b = el("b", "", x[1]); if (x[3]) b.style.color = x[3]; d.append(el("span", "muted small", x[0]), b); if (x[2]) d.appendChild(el("span", "small muted", x[2])); s.appendChild(d);
|
|
870
926
|
});
|
|
871
927
|
m.appendChild(s);
|
|
@@ -892,6 +948,12 @@ function studioAppPage(projectName, token = "") {
|
|
|
892
948
|
var line = el("div", "line"), gg = el("div", "grow");
|
|
893
949
|
gg.appendChild(el("strong", "", d.title));
|
|
894
950
|
d.scenarios.forEach(function(sc){ var sp = el("span", "small muted", t("provedBy") + (sc.title || sc.id)); sp.appendChild(el("span", "", " · ✓")); sp.lastChild.style.color = "var(--pass)"; gg.appendChild(sp); });
|
|
951
|
+
if (d.quality && (d.quality.coverage !== null || d.quality.mutation !== null)) {
|
|
952
|
+
var qs = [];
|
|
953
|
+
if (d.quality.coverage !== null) qs.push(t("covShort") + " " + d.quality.coverage + "%");
|
|
954
|
+
if (d.quality.mutation !== null) qs.push(t("mutShort") + " " + d.quality.mutation + "%");
|
|
955
|
+
gg.appendChild(el("span", "small", qs.join(" · ")));
|
|
956
|
+
}
|
|
895
957
|
line.append(gg, el("span", "small muted", adv() ? d.id : "")); wd.appendChild(line);
|
|
896
958
|
});
|
|
897
959
|
m.appendChild(wd);
|
|
@@ -928,6 +990,7 @@ function studioAppPage(projectName, token = "") {
|
|
|
928
990
|
var reads = [get("api/route").then(function(x){ cache.route = x; })];
|
|
929
991
|
var isProject = !cache.route || cache.route.isProject;
|
|
930
992
|
if (!cache.profiles || view === "project") reads.push(get("api/profiles").then(function(x){ cache.profiles = x; }));
|
|
993
|
+
if (view === "project" && isProject) reads.push(get("api/quality").then(function(x){ cache.quality = x; }));
|
|
931
994
|
if (view === "route" && isProject) reads.push(get("api/home").then(function(x){ cache.home = x; }).catch(function(){}));
|
|
932
995
|
if (view === "brief") { reads.push(get("api/modules").then(function(x){ cache.modules = x; })); if (!cache.brief || (cache.modules && cache.modules.job)) reads.push(get("api/brief").then(function(x){ cache.brief = x; })); }
|
|
933
996
|
if (view === "drafts") reads.push(get("api/modules").then(function(x){ cache.modules = x; }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"studio-app-page.js","sourceRoot":"","sources":["../../../scripts/lib/studio-app-page.ts"],"names":[],"mappings":";;AAUA,
|
|
1
|
+
{"version":3,"file":"studio-app-page.js","sourceRoot":"","sources":["../../../scripts/lib/studio-app-page.ts"],"names":[],"mappings":";;AAUA,sCAg/BC;AA1/BD;;;;;;;;;GASG;AACH,SAAgB,aAAa,CAAC,WAAmB,EAAE,KAAK,GAAG,EAAE;IAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACpD,OAAO;;;uCAG8B,GAAG;2BACf,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iRA+FiP,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAy4BvQ,CAAC;AAChB,CAAC"}
|
|
@@ -37,6 +37,7 @@ exports.drafts = drafts;
|
|
|
37
37
|
exports.questionsView = questionsView;
|
|
38
38
|
exports.buildView = buildView;
|
|
39
39
|
exports.startBuild = startBuild;
|
|
40
|
+
exports.saveSpecification = saveSpecification;
|
|
40
41
|
exports.stopBuild = stopBuild;
|
|
41
42
|
exports.home = home;
|
|
42
43
|
exports.gate = gate;
|
|
@@ -268,6 +269,9 @@ function startBuild(projectDir, reqs, profile = "") {
|
|
|
268
269
|
message: "None of those requirements is ready to build.",
|
|
269
270
|
};
|
|
270
271
|
}
|
|
272
|
+
const saved = saveSpecification(projectDir);
|
|
273
|
+
if (!saved.ok)
|
|
274
|
+
return saved;
|
|
271
275
|
const log = path.join(projectDir, ".harness", "events", "studio-run.log");
|
|
272
276
|
fs.mkdirSync(path.dirname(log), { recursive: true });
|
|
273
277
|
const out = fs.openSync(log, "a");
|
|
@@ -290,10 +294,86 @@ function startBuild(projectDir, reqs, profile = "") {
|
|
|
290
294
|
return {
|
|
291
295
|
ok: true,
|
|
292
296
|
started: chosen,
|
|
297
|
+
saved: saved.committed,
|
|
293
298
|
log: path.relative(projectDir, log).split(path.sep).join("/"),
|
|
294
299
|
pid: child.pid || 0,
|
|
295
300
|
};
|
|
296
301
|
}
|
|
302
|
+
/** What the studio and the drafting write: the specification, never code. */
|
|
303
|
+
const SPECIFICATION = [
|
|
304
|
+
/^docs\/brief\.md$/,
|
|
305
|
+
/^spec\.md$/,
|
|
306
|
+
/^docs\/specs\//,
|
|
307
|
+
/^features\//,
|
|
308
|
+
/^harness\.config\.yaml$/,
|
|
309
|
+
/^\.harness\/(?:profiles\.yaml|prompt-prefix\.md)$/,
|
|
310
|
+
];
|
|
311
|
+
/** Uncommitted paths, from `git status --porcelain -z`: renames give their new path. */
|
|
312
|
+
function uncommitted(projectDir) {
|
|
313
|
+
const r = (0, node_child_process_1.spawnSync)("git", ["-C", projectDir, "status", "--porcelain", "-z", "-uall"], {
|
|
314
|
+
encoding: "utf8",
|
|
315
|
+
});
|
|
316
|
+
if (r.status !== 0)
|
|
317
|
+
return null;
|
|
318
|
+
const parts = r.stdout.split("\0").filter(Boolean);
|
|
319
|
+
const out = [];
|
|
320
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
321
|
+
const status = parts[i].slice(0, 2);
|
|
322
|
+
out.push(parts[i].slice(3));
|
|
323
|
+
if (/[RC]/.test(status))
|
|
324
|
+
i += 1; // the old path of a rename follows
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Commit the specification the studio wrote, so the harness finds the clean
|
|
330
|
+
* tree it needs (REQ-233). Anything else uncommitted is the person's work in
|
|
331
|
+
* progress: the build is refused and the files are named, never committed.
|
|
332
|
+
*/
|
|
333
|
+
function saveSpecification(projectDir) {
|
|
334
|
+
const dirty = uncommitted(projectDir);
|
|
335
|
+
if (dirty === null) {
|
|
336
|
+
return { ok: false, code: "build_no_git", message: "This folder is not a git repository." };
|
|
337
|
+
}
|
|
338
|
+
if (dirty.length === 0)
|
|
339
|
+
return { ok: true, committed: [] };
|
|
340
|
+
const other = dirty.filter((f) => !SPECIFICATION.some((re) => re.test(f)));
|
|
341
|
+
if (other.length) {
|
|
342
|
+
return {
|
|
343
|
+
ok: false,
|
|
344
|
+
code: "build_uncommitted",
|
|
345
|
+
message: `Uncommitted files that are not the specification: ${other.slice(0, 8).join(", ")}` +
|
|
346
|
+
`${other.length > 8 ? ` and ${other.length - 8} more` : ""}. Commit or stash them first.`,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const email = (0, node_child_process_1.spawnSync)("git", ["-C", projectDir, "config", "user.email"], { encoding: "utf8" });
|
|
350
|
+
if (!String(email.stdout || "").trim()) {
|
|
351
|
+
return {
|
|
352
|
+
ok: false,
|
|
353
|
+
code: "build_no_identity",
|
|
354
|
+
message: 'Git does not know who you are. Run git config --global user.name "Your Name" and git config --global user.email you@example.com.',
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const add = (0, node_child_process_1.spawnSync)("git", ["-C", projectDir, "add", "--", ...dirty], { encoding: "utf8" });
|
|
358
|
+
const commit = add.status === 0
|
|
359
|
+
? (0, node_child_process_1.spawnSync)("git", [
|
|
360
|
+
"-C",
|
|
361
|
+
projectDir,
|
|
362
|
+
"commit",
|
|
363
|
+
"-q",
|
|
364
|
+
"-m",
|
|
365
|
+
"chore(spec): save the specification from the studio",
|
|
366
|
+
], { encoding: "utf8" })
|
|
367
|
+
: add;
|
|
368
|
+
if (commit.status !== 0) {
|
|
369
|
+
return {
|
|
370
|
+
ok: false,
|
|
371
|
+
code: "build_commit_failed",
|
|
372
|
+
message: `git could not commit the specification: ${(commit.stderr || commit.stdout || "").trim()}`,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
return { ok: true, committed: dirty };
|
|
376
|
+
}
|
|
297
377
|
/** Ask the run in progress to stop after its current requirement (REQ-209). */
|
|
298
378
|
function stopBuild(projectDir) {
|
|
299
379
|
if (!(0, harness_guard_1.runLockHolder)(projectDir))
|