ai-spec-dev 0.38.0 → 0.42.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/.ai-spec-workspace.json +17 -0
- package/.ai-spec.json +7 -0
- package/cli/commands/create.ts +9 -1176
- package/cli/commands/dashboard.ts +1 -1
- package/cli/pipeline/helpers.ts +34 -0
- package/cli/pipeline/multi-repo.ts +483 -0
- package/cli/pipeline/single-repo.ts +764 -0
- package/cli/utils.ts +2 -0
- package/core/cli-ui.ts +136 -0
- package/core/code-generator.ts +56 -343
- package/core/codegen/helpers.ts +219 -0
- package/core/codegen/topo-sort.ts +98 -0
- package/core/constitution-consolidator.ts +2 -2
- package/core/dsl-coverage-checker.ts +298 -0
- package/core/dsl-extractor.ts +19 -46
- package/core/dsl-feedback.ts +1 -1
- package/core/dsl-validator.ts +74 -0
- package/core/error-feedback.ts +99 -13
- package/core/frontend-context-loader.ts +27 -5
- package/core/knowledge-memory.ts +52 -0
- package/core/mock/fixtures.ts +89 -0
- package/core/mock/proxy.ts +380 -0
- package/core/mock-server-generator.ts +12 -460
- package/core/provider-utils.ts +8 -7
- package/core/requirement-decomposer.ts +4 -28
- package/core/reviewer.ts +1 -1
- package/core/safe-json.ts +76 -0
- package/core/spec-updater.ts +5 -21
- package/core/token-budget.ts +124 -0
- package/core/vcr.ts +20 -1
- package/demo-backend/.ai-spec-constitution.md +65 -0
- package/demo-backend/package.json +21 -0
- package/demo-backend/prisma/schema.prisma +22 -0
- package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.dsl.json +186 -0
- package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.md +211 -0
- package/demo-backend/src/controllers/bookmark.controller.test.ts +255 -0
- package/demo-backend/src/controllers/bookmark.controller.ts +187 -0
- package/demo-backend/src/index.ts +17 -0
- package/demo-backend/src/routes/bookmark.routes.test.ts +264 -0
- package/demo-backend/src/routes/bookmark.routes.ts +11 -0
- package/demo-backend/src/routes/index.ts +8 -0
- package/demo-backend/src/services/bookmark.service.test.ts +433 -0
- package/demo-backend/src/services/bookmark.service.ts +261 -0
- package/demo-backend/tsconfig.json +12 -0
- package/demo-frontend/.ai-spec-constitution.md +95 -0
- package/demo-frontend/package.json +23 -0
- package/demo-frontend/src/App.tsx +12 -0
- package/demo-frontend/src/main.tsx +9 -0
- package/demo-frontend/tsconfig.json +13 -0
- package/dist/cli/index.js +4351 -3666
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +3997 -3312
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +18 -16
- package/dist/index.d.ts +18 -16
- package/dist/index.js +388 -188
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +386 -186
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/tests/auto-consolidation.test.ts +109 -0
- package/tests/combined-generator.test.ts +81 -0
- package/tests/constitution-consolidator.test.ts +161 -0
- package/tests/constitution-generator.test.ts +94 -0
- package/tests/contract-bridge.test.ts +201 -0
- package/tests/design-dialogue.test.ts +108 -0
- package/tests/dsl-coverage-checker.test.ts +230 -0
- package/tests/dsl-feedback.test.ts +45 -0
- package/tests/dsl-validator-xref.test.ts +99 -0
- package/tests/error-feedback-repair.test.ts +319 -0
- package/tests/error-feedback-validation.test.ts +91 -0
- package/tests/frontend-context-loader.test.ts +609 -0
- package/tests/global-constitution.test.ts +110 -0
- package/tests/key-store.test.ts +73 -0
- package/tests/knowledge-memory.test.ts +327 -0
- package/tests/project-index.test.ts +206 -0
- package/tests/prompt-hasher.test.ts +19 -0
- package/tests/requirement-decomposer.test.ts +171 -0
- package/tests/reviewer.test.ts +4 -1
- package/tests/run-logger.test.ts +289 -0
- package/tests/run-snapshot.test.ts +113 -0
- package/tests/safe-json.test.ts +63 -0
- package/tests/spec-updater.test.ts +161 -0
- package/tests/test-generator.test.ts +146 -0
- package/tests/token-budget.test.ts +124 -0
- package/tests/vcr-hash.test.ts +101 -0
- package/tests/workspace-loader.test.ts +277 -0
- package/RELEASE_LOG.md +0 -2731
- package/purpose.md +0 -1294
package/dist/index.js
CHANGED
|
@@ -182,9 +182,77 @@ CRITICAL \u2014 \u5386\u53F2\u6559\u8BAD\u5E94\u7528\uFF08Accumulated Lessons\uF
|
|
|
182
182
|
3. \u5BF9\u4E8E\u6BCF\u6761\u76F4\u63A5\u76F8\u5173\u7684\u6559\u8BAD\uFF0C\u5728 \xA78 \u5B9E\u65BD\u8981\u70B9\u672B\u5C3E\u8FFD\u52A0\u4E00\u884C\uFF1A\u300C\u26A0 \u57FA\u4E8E\u5386\u53F2\u6559\u8BAD\uFF1A[\u7B80\u8FF0\u672C\u6B21 spec \u5982\u4F55\u89C4\u907F\u8BE5\u95EE\u9898]\u300D
|
|
183
183
|
4. \u5982\u65E0\u76F8\u5173\u6559\u8BAD\uFF0C\xA78 \u4E0D\u5FC5\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9`;
|
|
184
184
|
|
|
185
|
-
// core/
|
|
185
|
+
// core/cli-ui.ts
|
|
186
186
|
var import_chalk = __toESM(require("chalk"));
|
|
187
|
-
var
|
|
187
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
188
|
+
function startSpinner(text) {
|
|
189
|
+
const isTTY = process.stderr.isTTY;
|
|
190
|
+
let frame = 0;
|
|
191
|
+
let currentText = text;
|
|
192
|
+
let stopped = false;
|
|
193
|
+
function render() {
|
|
194
|
+
if (stopped) return;
|
|
195
|
+
const symbol = import_chalk.default.cyan(SPINNER_FRAMES[frame % SPINNER_FRAMES.length]);
|
|
196
|
+
if (isTTY) {
|
|
197
|
+
process.stderr.write(`\r ${symbol} ${currentText}${" ".repeat(10)}`);
|
|
198
|
+
}
|
|
199
|
+
frame++;
|
|
200
|
+
}
|
|
201
|
+
if (!isTTY) {
|
|
202
|
+
process.stderr.write(` \u2026 ${currentText}
|
|
203
|
+
`);
|
|
204
|
+
}
|
|
205
|
+
const timer = setInterval(render, 80);
|
|
206
|
+
render();
|
|
207
|
+
return {
|
|
208
|
+
update(newText) {
|
|
209
|
+
currentText = newText;
|
|
210
|
+
},
|
|
211
|
+
stop(finalText) {
|
|
212
|
+
if (stopped) return;
|
|
213
|
+
stopped = true;
|
|
214
|
+
clearInterval(timer);
|
|
215
|
+
if (isTTY) {
|
|
216
|
+
process.stderr.write(`\r${" ".repeat(currentText.length + 20)}\r`);
|
|
217
|
+
}
|
|
218
|
+
if (finalText) {
|
|
219
|
+
process.stderr.write(` ${finalText}
|
|
220
|
+
`);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
succeed(successText) {
|
|
224
|
+
this.stop(import_chalk.default.green(`\u2714 ${successText}`));
|
|
225
|
+
},
|
|
226
|
+
fail(failText) {
|
|
227
|
+
this.stop(import_chalk.default.red(`\u2718 ${failText}`));
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async function retryCountdown(opts) {
|
|
232
|
+
const { attempt, maxAttempts, waitMs, errorMessage, label } = opts;
|
|
233
|
+
const isTTY = process.stderr.isTTY;
|
|
234
|
+
const shortErr = errorMessage.length > 120 ? errorMessage.slice(0, 117) + "..." : errorMessage;
|
|
235
|
+
process.stderr.write("\n");
|
|
236
|
+
process.stderr.write(import_chalk.default.yellow(` \u250C\u2500 Retry ${attempt}/${maxAttempts} `) + import_chalk.default.gray(`[${label}]`) + import_chalk.default.yellow(` ${"\u2500".repeat(Math.max(1, 40 - label.length))}
|
|
237
|
+
`));
|
|
238
|
+
process.stderr.write(import_chalk.default.yellow(` \u2502 `) + import_chalk.default.white(shortErr) + "\n");
|
|
239
|
+
process.stderr.write(import_chalk.default.yellow(` \u2502 `) + import_chalk.default.gray(`Waiting before retry...`) + "\n");
|
|
240
|
+
const totalSeconds = Math.ceil(waitMs / 1e3);
|
|
241
|
+
for (let s = totalSeconds; s > 0; s--) {
|
|
242
|
+
const bar = import_chalk.default.green("\u2588".repeat(totalSeconds - s)) + import_chalk.default.gray("\u2591".repeat(s));
|
|
243
|
+
const line = import_chalk.default.yellow(` \u2502 `) + `${bar} ${import_chalk.default.bold.white(`${s}s`)}`;
|
|
244
|
+
if (isTTY) {
|
|
245
|
+
process.stderr.write(`\r${line}${" ".repeat(10)}`);
|
|
246
|
+
}
|
|
247
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
248
|
+
}
|
|
249
|
+
if (isTTY) {
|
|
250
|
+
process.stderr.write(`\r${" ".repeat(70)}\r`);
|
|
251
|
+
}
|
|
252
|
+
process.stderr.write(import_chalk.default.yellow(` \u2514\u2500 `) + import_chalk.default.cyan(`Retrying now...`) + "\n\n");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// core/provider-utils.ts
|
|
188
256
|
var ProviderError = class extends Error {
|
|
189
257
|
constructor(message, kind, originalError) {
|
|
190
258
|
super(message);
|
|
@@ -268,11 +336,14 @@ async function withReliability(fn, opts) {
|
|
|
268
336
|
throw classifyError(err, label);
|
|
269
337
|
}
|
|
270
338
|
const waitMs = attempt === 0 ? 2e3 : 6e3;
|
|
271
|
-
console.warn(
|
|
272
|
-
import_chalk.default.yellow(` \u26A0 ${label} failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${waitMs / 1e3}s`) + import_chalk.default.gray(` \u2014 ${err.message}`)
|
|
273
|
-
);
|
|
274
339
|
onRetry?.(attempt + 1, err);
|
|
275
|
-
await
|
|
340
|
+
await retryCountdown({
|
|
341
|
+
attempt: attempt + 1,
|
|
342
|
+
maxAttempts: retries + 1,
|
|
343
|
+
waitMs,
|
|
344
|
+
errorMessage: err.message ?? String(err),
|
|
345
|
+
label
|
|
346
|
+
});
|
|
276
347
|
}
|
|
277
348
|
}
|
|
278
349
|
throw new Error("unreachable");
|
|
@@ -4173,8 +4244,8 @@ ${currentSpec}`,
|
|
|
4173
4244
|
};
|
|
4174
4245
|
|
|
4175
4246
|
// core/code-generator.ts
|
|
4176
|
-
var
|
|
4177
|
-
var
|
|
4247
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
4248
|
+
var import_child_process2 = require("child_process");
|
|
4178
4249
|
var path6 = __toESM(require("path"));
|
|
4179
4250
|
var fs10 = __toESM(require("fs-extra"));
|
|
4180
4251
|
|
|
@@ -4751,7 +4822,7 @@ async function updateTaskStatus(specFilePath, taskId, status) {
|
|
|
4751
4822
|
}
|
|
4752
4823
|
|
|
4753
4824
|
// core/dsl-extractor.ts
|
|
4754
|
-
var
|
|
4825
|
+
var import_chalk7 = __toESM(require("chalk"));
|
|
4755
4826
|
var fs6 = __toESM(require("fs-extra"));
|
|
4756
4827
|
var path4 = __toESM(require("path"));
|
|
4757
4828
|
var import_prompts2 = require("@inquirer/prompts");
|
|
@@ -4840,6 +4911,7 @@ function validateDsl(raw) {
|
|
|
4840
4911
|
}
|
|
4841
4912
|
}
|
|
4842
4913
|
}
|
|
4914
|
+
crossReferenceChecks(obj, errors);
|
|
4843
4915
|
if (errors.length > 0) {
|
|
4844
4916
|
return { valid: false, errors };
|
|
4845
4917
|
}
|
|
@@ -5071,6 +5143,64 @@ function validateComponent(raw, path10, errors) {
|
|
|
5071
5143
|
}
|
|
5072
5144
|
}
|
|
5073
5145
|
}
|
|
5146
|
+
function crossReferenceChecks(obj, errors) {
|
|
5147
|
+
const models = Array.isArray(obj["models"]) ? obj["models"] : [];
|
|
5148
|
+
const endpoints = Array.isArray(obj["endpoints"]) ? obj["endpoints"] : [];
|
|
5149
|
+
const components = Array.isArray(obj["components"]) ? obj["components"] : [];
|
|
5150
|
+
const seenRoutes = /* @__PURE__ */ new Map();
|
|
5151
|
+
for (let i = 0; i < endpoints.length; i++) {
|
|
5152
|
+
const ep = endpoints[i];
|
|
5153
|
+
if (typeof ep?.["method"] === "string" && typeof ep?.["path"] === "string") {
|
|
5154
|
+
const route = `${ep["method"].toUpperCase()} ${ep["path"]}`;
|
|
5155
|
+
if (seenRoutes.has(route)) {
|
|
5156
|
+
errors.push({
|
|
5157
|
+
path: `endpoints[${i}]`,
|
|
5158
|
+
message: `Duplicate route "${route}" \u2014 also defined at endpoints[${seenRoutes.get(route)}]`
|
|
5159
|
+
});
|
|
5160
|
+
} else {
|
|
5161
|
+
seenRoutes.set(route, i);
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
const modelNames = new Set(
|
|
5166
|
+
models.filter((m) => typeof m?.["name"] === "string").map((m) => m["name"])
|
|
5167
|
+
);
|
|
5168
|
+
for (let i = 0; i < models.length; i++) {
|
|
5169
|
+
const m = models[i];
|
|
5170
|
+
if (!Array.isArray(m?.["relations"])) continue;
|
|
5171
|
+
for (const rel of m["relations"]) {
|
|
5172
|
+
if (typeof rel !== "string") continue;
|
|
5173
|
+
const refMatch = rel.match(/(?:hasMany|hasOne|belongsTo|manyToMany)\s+(\w+)/i);
|
|
5174
|
+
if (refMatch) {
|
|
5175
|
+
const refName = refMatch[1];
|
|
5176
|
+
if (!modelNames.has(refName)) {
|
|
5177
|
+
errors.push({
|
|
5178
|
+
path: `models[${i}].relations`,
|
|
5179
|
+
message: `Relation references model "${refName}" which is not defined in models[]`
|
|
5180
|
+
});
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
const endpointIds = new Set(
|
|
5186
|
+
endpoints.filter((e) => typeof e?.["id"] === "string").map((e) => e["id"])
|
|
5187
|
+
);
|
|
5188
|
+
if (endpointIds.size > 0) {
|
|
5189
|
+
for (let i = 0; i < components.length; i++) {
|
|
5190
|
+
const c = components[i];
|
|
5191
|
+
if (!Array.isArray(c?.["apiCalls"])) continue;
|
|
5192
|
+
for (const call of c["apiCalls"]) {
|
|
5193
|
+
if (typeof call !== "string") continue;
|
|
5194
|
+
if (!endpointIds.has(call)) {
|
|
5195
|
+
errors.push({
|
|
5196
|
+
path: `components[${i}].apiCalls`,
|
|
5197
|
+
message: `References endpoint "${call}" which is not defined in endpoints[]`
|
|
5198
|
+
});
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5074
5204
|
function requireNonEmptyString(v2, path10, errors) {
|
|
5075
5205
|
if (typeof v2 !== "string" || v2.trim().length === 0) {
|
|
5076
5206
|
errors.push({
|
|
@@ -5085,6 +5215,26 @@ function typeLabel(v2) {
|
|
|
5085
5215
|
return typeof v2;
|
|
5086
5216
|
}
|
|
5087
5217
|
|
|
5218
|
+
// core/token-budget.ts
|
|
5219
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
5220
|
+
var CJK_RANGE = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g;
|
|
5221
|
+
function estimateTokens(text) {
|
|
5222
|
+
if (!text) return 0;
|
|
5223
|
+
const cjkCount = (text.match(CJK_RANGE) ?? []).length;
|
|
5224
|
+
const nonCjkLength = text.length - cjkCount;
|
|
5225
|
+
return Math.ceil(cjkCount + nonCjkLength / 4);
|
|
5226
|
+
}
|
|
5227
|
+
var DEFAULT_TOKEN_BUDGETS = {
|
|
5228
|
+
gemini: 9e5,
|
|
5229
|
+
claude: 18e4,
|
|
5230
|
+
openai: 12e4,
|
|
5231
|
+
deepseek: 6e4,
|
|
5232
|
+
default: 1e5
|
|
5233
|
+
};
|
|
5234
|
+
function getDefaultBudget(providerName) {
|
|
5235
|
+
return DEFAULT_TOKEN_BUDGETS[providerName] ?? DEFAULT_TOKEN_BUDGETS.default;
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5088
5238
|
// core/dsl-extractor.ts
|
|
5089
5239
|
function dslFilePath(specFilePath) {
|
|
5090
5240
|
const dir = path4.dirname(specFilePath);
|
|
@@ -5243,7 +5393,9 @@ var ROUTING_LIBS = [
|
|
|
5243
5393
|
["@tanstack/react-router", "tanstack-router"],
|
|
5244
5394
|
["react-navigation", "react-navigation"],
|
|
5245
5395
|
["expo-router", "expo-router"],
|
|
5246
|
-
["vue-router", "vue-router"]
|
|
5396
|
+
["vue-router", "vue-router"],
|
|
5397
|
+
["@solidjs/router", "solid-router"],
|
|
5398
|
+
["@builder.io/qwik-city", "qwik-city"]
|
|
5247
5399
|
];
|
|
5248
5400
|
async function loadFrontendContext(projectRoot) {
|
|
5249
5401
|
const ctx = {
|
|
@@ -5275,6 +5427,18 @@ async function loadFrontendContext(projectRoot) {
|
|
|
5275
5427
|
const has = (name) => depKeys.includes(name);
|
|
5276
5428
|
if (has("react-native") || has("expo")) {
|
|
5277
5429
|
ctx.framework = "react-native";
|
|
5430
|
+
} else if (has("@sveltejs/kit")) {
|
|
5431
|
+
ctx.framework = "sveltekit";
|
|
5432
|
+
} else if (has("svelte")) {
|
|
5433
|
+
ctx.framework = "svelte";
|
|
5434
|
+
} else if (has("@builder.io/qwik")) {
|
|
5435
|
+
ctx.framework = "qwik";
|
|
5436
|
+
} else if (has("@remix-run/react") || has("@remix-run/node")) {
|
|
5437
|
+
ctx.framework = "remix";
|
|
5438
|
+
} else if (has("astro")) {
|
|
5439
|
+
ctx.framework = "astro";
|
|
5440
|
+
} else if (has("solid-js")) {
|
|
5441
|
+
ctx.framework = "solid";
|
|
5278
5442
|
} else if (has("next")) {
|
|
5279
5443
|
ctx.framework = "next";
|
|
5280
5444
|
} else if (has("react")) {
|
|
@@ -5301,6 +5465,12 @@ async function loadFrontendContext(projectRoot) {
|
|
|
5301
5465
|
if (ctx.framework === "next") {
|
|
5302
5466
|
const hasAppDir = await fs7.pathExists(path5.join(projectRoot, "app"));
|
|
5303
5467
|
ctx.routingPattern = hasAppDir ? "next-app-router" : "next-pages-router";
|
|
5468
|
+
} else if (ctx.framework === "sveltekit") {
|
|
5469
|
+
ctx.routingPattern = "sveltekit-file-router";
|
|
5470
|
+
} else if (ctx.framework === "remix") {
|
|
5471
|
+
ctx.routingPattern = "remix-file-router";
|
|
5472
|
+
} else if (ctx.framework === "astro") {
|
|
5473
|
+
ctx.routingPattern = "astro-file-router";
|
|
5304
5474
|
} else {
|
|
5305
5475
|
for (const [lib, label] of ROUTING_LIBS) {
|
|
5306
5476
|
if (has(lib)) {
|
|
@@ -5686,13 +5856,14 @@ function getActiveSnapshot() {
|
|
|
5686
5856
|
|
|
5687
5857
|
// core/run-logger.ts
|
|
5688
5858
|
var fs9 = __toESM(require("fs-extra"));
|
|
5689
|
-
var
|
|
5859
|
+
var import_chalk8 = __toESM(require("chalk"));
|
|
5690
5860
|
var _activeLogger = null;
|
|
5691
5861
|
function getActiveLogger() {
|
|
5692
5862
|
return _activeLogger;
|
|
5693
5863
|
}
|
|
5694
5864
|
|
|
5695
|
-
// core/
|
|
5865
|
+
// core/codegen/helpers.ts
|
|
5866
|
+
var import_child_process = require("child_process");
|
|
5696
5867
|
function buildSharedConfigSection(context) {
|
|
5697
5868
|
if (!context?.sharedConfigFiles || context.sharedConfigFiles.length === 0) return "";
|
|
5698
5869
|
const lines = [
|
|
@@ -5824,7 +5995,7 @@ function buildGeneratedFilesSection(cache) {
|
|
|
5824
5995
|
}
|
|
5825
5996
|
function isRtkAvailable() {
|
|
5826
5997
|
try {
|
|
5827
|
-
(0, import_child_process.execSync)("rtk --version", { stdio: "ignore" });
|
|
5998
|
+
(0, import_child_process.execSync)("rtk --version", { stdio: "ignore", timeout: 1e4 });
|
|
5828
5999
|
return true;
|
|
5829
6000
|
} catch {
|
|
5830
6001
|
return false;
|
|
@@ -5848,6 +6019,73 @@ function parseJsonArray(text) {
|
|
|
5848
6019
|
}
|
|
5849
6020
|
return [];
|
|
5850
6021
|
}
|
|
6022
|
+
|
|
6023
|
+
// core/codegen/topo-sort.ts
|
|
6024
|
+
var import_chalk9 = __toESM(require("chalk"));
|
|
6025
|
+
function topoSortLayerTasks(tasks) {
|
|
6026
|
+
if (tasks.length <= 1) return [tasks];
|
|
6027
|
+
const idSet = new Set(tasks.map((t) => t.id));
|
|
6028
|
+
const taskById = new Map(tasks.map((t) => [t.id, t]));
|
|
6029
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
6030
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
6031
|
+
for (const task of tasks) {
|
|
6032
|
+
inDegree.set(task.id, 0);
|
|
6033
|
+
dependents.set(task.id, []);
|
|
6034
|
+
}
|
|
6035
|
+
for (const task of tasks) {
|
|
6036
|
+
const intraDeps = task.dependencies.filter((dep) => idSet.has(dep));
|
|
6037
|
+
inDegree.set(task.id, intraDeps.length);
|
|
6038
|
+
for (const dep of intraDeps) {
|
|
6039
|
+
dependents.get(dep).push(task.id);
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
const batches = [];
|
|
6043
|
+
const remaining = new Set(tasks.map((t) => t.id));
|
|
6044
|
+
while (remaining.size > 0) {
|
|
6045
|
+
const batch = [...remaining].filter((id) => inDegree.get(id) === 0).map((id) => taskById.get(id));
|
|
6046
|
+
if (batch.length === 0) {
|
|
6047
|
+
batches.push([...remaining].map((id) => taskById.get(id)));
|
|
6048
|
+
break;
|
|
6049
|
+
}
|
|
6050
|
+
batches.push(batch);
|
|
6051
|
+
for (const task of batch) {
|
|
6052
|
+
remaining.delete(task.id);
|
|
6053
|
+
for (const dependent of dependents.get(task.id)) {
|
|
6054
|
+
inDegree.set(dependent, inDegree.get(dependent) - 1);
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
6058
|
+
return batches;
|
|
6059
|
+
}
|
|
6060
|
+
var LAYER_ICONS = {
|
|
6061
|
+
data: "\u{1F4BE}",
|
|
6062
|
+
infra: "\u2699\uFE0F ",
|
|
6063
|
+
service: "\u{1F527}",
|
|
6064
|
+
api: "\u{1F310}",
|
|
6065
|
+
view: "\u{1F5A5}\uFE0F ",
|
|
6066
|
+
route: "\u{1F5FA}\uFE0F ",
|
|
6067
|
+
test: "\u{1F9EA}"
|
|
6068
|
+
};
|
|
6069
|
+
function printTaskProgress(completed, total, task, mode) {
|
|
6070
|
+
const pct = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
6071
|
+
const barWidth = 20;
|
|
6072
|
+
const filled = Math.round(pct / 100 * barWidth);
|
|
6073
|
+
const bar = import_chalk9.default.green("\u2588".repeat(filled)) + import_chalk9.default.gray("\u2591".repeat(barWidth - filled));
|
|
6074
|
+
const icon = LAYER_ICONS[task.layer] ?? " ";
|
|
6075
|
+
if (mode === "skip") {
|
|
6076
|
+
console.log(
|
|
6077
|
+
import_chalk9.default.gray(`
|
|
6078
|
+
[${bar}] ${pct}% \u2713 ${task.id} ${icon} ${task.title} \u2014 already done`)
|
|
6079
|
+
);
|
|
6080
|
+
} else {
|
|
6081
|
+
console.log(
|
|
6082
|
+
import_chalk9.default.bold(`
|
|
6083
|
+
[${bar}] ${pct}% \u2192 ${task.id} ${icon} ${task.title}`)
|
|
6084
|
+
);
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
|
|
6088
|
+
// core/code-generator.ts
|
|
5851
6089
|
var CodeGenerator = class {
|
|
5852
6090
|
constructor(provider, mode = "claude-code") {
|
|
5853
6091
|
this.provider = provider;
|
|
@@ -5858,13 +6096,13 @@ var CodeGenerator = class {
|
|
|
5858
6096
|
let effectiveMode = this.mode;
|
|
5859
6097
|
if (effectiveMode === "claude-code" && this.provider.providerName !== "claude") {
|
|
5860
6098
|
console.log(
|
|
5861
|
-
|
|
6099
|
+
import_chalk10.default.yellow(
|
|
5862
6100
|
`
|
|
5863
6101
|
\u26A0 codegen \u6A21\u5F0F "claude-code" \u9700\u8981 Claude\uFF0C\u4F46\u5F53\u524D provider \u662F "${this.provider.providerName}"\u3002`
|
|
5864
6102
|
)
|
|
5865
6103
|
);
|
|
5866
|
-
console.log(
|
|
5867
|
-
console.log(
|
|
6104
|
+
console.log(import_chalk10.default.gray(` \u81EA\u52A8\u5207\u6362\u5230 "api" \u6A21\u5F0F\uFF08\u4F7F\u7528 ${this.provider.providerName}/${this.provider.modelName} \u751F\u6210\u4EE3\u7801\uFF09\u3002`));
|
|
6105
|
+
console.log(import_chalk10.default.gray(` \u63D0\u793A\uFF1A\u8FD0\u884C \`ai-spec config --codegen api\` \u53EF\u56FA\u5316\u6B64\u8BBE\u7F6E\u3002
|
|
5868
6106
|
`));
|
|
5869
6107
|
effectiveMode = "api";
|
|
5870
6108
|
}
|
|
@@ -5882,23 +6120,23 @@ var CodeGenerator = class {
|
|
|
5882
6120
|
// ── Mode: claude-code ──────────────────────────────────────────────────────
|
|
5883
6121
|
isClaudeCLIAvailable() {
|
|
5884
6122
|
try {
|
|
5885
|
-
(0,
|
|
6123
|
+
(0, import_child_process2.execSync)("claude --version", { stdio: "ignore", timeout: 1e4 });
|
|
5886
6124
|
return true;
|
|
5887
6125
|
} catch {
|
|
5888
6126
|
return false;
|
|
5889
6127
|
}
|
|
5890
6128
|
}
|
|
5891
6129
|
async runClaudeCode(specFilePath, workingDir, options = {}) {
|
|
5892
|
-
console.log(
|
|
6130
|
+
console.log(import_chalk10.default.blue("\n\u2500\u2500\u2500 Code Generation: Claude Code CLI \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5893
6131
|
if (!this.isClaudeCLIAvailable()) {
|
|
5894
|
-
console.log(
|
|
5895
|
-
console.log(
|
|
6132
|
+
console.log(import_chalk10.default.yellow(" \u26A0\uFE0F Claude Code CLI not found. Falling back to plan mode."));
|
|
6133
|
+
console.log(import_chalk10.default.gray(" Install: npm install -g @anthropic-ai/claude-code"));
|
|
5896
6134
|
return this.runPlanMode(specFilePath);
|
|
5897
6135
|
}
|
|
5898
6136
|
const rtkAvailable = isRtkAvailable();
|
|
5899
6137
|
const claudeCmd = rtkAvailable ? "rtk claude" : "claude";
|
|
5900
6138
|
if (rtkAvailable) {
|
|
5901
|
-
console.log(
|
|
6139
|
+
console.log(import_chalk10.default.green(" \u2713 RTK detected \u2014 using rtk claude for token savings"));
|
|
5902
6140
|
}
|
|
5903
6141
|
const tasks = await loadTasksForSpec(specFilePath);
|
|
5904
6142
|
if (options.auto && tasks && tasks.length > 0) {
|
|
@@ -5914,28 +6152,28 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
|
|
|
5914
6152
|
const promptFile = path6.join(workingDir, ".claude-prompt.txt");
|
|
5915
6153
|
await fs10.writeFile(promptFile, promptContent, "utf-8");
|
|
5916
6154
|
if (options.auto) {
|
|
5917
|
-
console.log(
|
|
5918
|
-
console.log(
|
|
6155
|
+
console.log(import_chalk10.default.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
|
|
6156
|
+
console.log(import_chalk10.default.gray(` Spec: ${specFilePath}`));
|
|
5919
6157
|
try {
|
|
5920
|
-
(0,
|
|
6158
|
+
(0, import_child_process2.spawnSync)(claudeCmd, ["-p", promptContent], {
|
|
5921
6159
|
cwd: workingDir,
|
|
5922
6160
|
stdio: "inherit",
|
|
5923
6161
|
shell: false
|
|
5924
6162
|
});
|
|
5925
|
-
console.log(
|
|
6163
|
+
console.log(import_chalk10.default.green("\n \u2714 Claude Code completed."));
|
|
5926
6164
|
} catch {
|
|
5927
|
-
console.log(
|
|
6165
|
+
console.log(import_chalk10.default.yellow("\n Claude Code exited. Check output above."));
|
|
5928
6166
|
}
|
|
5929
6167
|
} else {
|
|
5930
|
-
console.log(
|
|
5931
|
-
console.log(
|
|
5932
|
-
if (tasks) console.log(
|
|
5933
|
-
console.log(
|
|
6168
|
+
console.log(import_chalk10.default.cyan(` \u{1F680} Launching ${claudeCmd} in: ${workingDir}`));
|
|
6169
|
+
console.log(import_chalk10.default.gray(` Spec: ${specFilePath}`));
|
|
6170
|
+
if (tasks) console.log(import_chalk10.default.gray(` Tasks: ${tasks.length} tasks loaded into .claude-prompt.txt`));
|
|
6171
|
+
console.log(import_chalk10.default.gray(" Prompt pre-loaded in .claude-prompt.txt\n"));
|
|
5934
6172
|
try {
|
|
5935
|
-
(0,
|
|
5936
|
-
console.log(
|
|
6173
|
+
(0, import_child_process2.execSync)(claudeCmd, { cwd: workingDir, stdio: "inherit" });
|
|
6174
|
+
console.log(import_chalk10.default.green("\n \u2714 Claude Code session completed."));
|
|
5937
6175
|
} catch {
|
|
5938
|
-
console.log(
|
|
6176
|
+
console.log(import_chalk10.default.yellow("\n Claude Code session ended. Continuing workflow."));
|
|
5939
6177
|
}
|
|
5940
6178
|
}
|
|
5941
6179
|
}
|
|
@@ -5948,10 +6186,10 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
|
|
|
5948
6186
|
const pending = tasks.filter((t) => t.status !== "done");
|
|
5949
6187
|
const doneCount = tasks.length - pending.length;
|
|
5950
6188
|
if (options.resume && doneCount > 0) {
|
|
5951
|
-
console.log(
|
|
6189
|
+
console.log(import_chalk10.default.cyan(`
|
|
5952
6190
|
Resuming: ${doneCount}/${tasks.length} tasks already done \u2014 skipping.`));
|
|
5953
6191
|
} else {
|
|
5954
|
-
console.log(
|
|
6192
|
+
console.log(import_chalk10.default.cyan(`
|
|
5955
6193
|
Incremental mode: ${tasks.length} tasks`));
|
|
5956
6194
|
}
|
|
5957
6195
|
let completed = doneCount;
|
|
@@ -5972,7 +6210,7 @@ Full spec is at: ${specFilePath}
|
|
|
5972
6210
|
Implement ONLY this task. Do not implement other tasks.`;
|
|
5973
6211
|
let taskStatus = "done";
|
|
5974
6212
|
try {
|
|
5975
|
-
(0,
|
|
6213
|
+
(0, import_child_process2.spawnSync)(claudeCmd, ["-p", taskPrompt], {
|
|
5976
6214
|
cwd: workingDir,
|
|
5977
6215
|
stdio: "inherit",
|
|
5978
6216
|
shell: false
|
|
@@ -5980,33 +6218,33 @@ Implement ONLY this task. Do not implement other tasks.`;
|
|
|
5980
6218
|
completed++;
|
|
5981
6219
|
} catch {
|
|
5982
6220
|
taskStatus = "failed";
|
|
5983
|
-
console.log(
|
|
6221
|
+
console.log(import_chalk10.default.yellow(`
|
|
5984
6222
|
\u26A0 Task ${task.id} exited with error \u2014 marked as failed. Re-run with --resume to retry.`));
|
|
5985
6223
|
}
|
|
5986
6224
|
await updateTaskStatus(specFilePath, task.id, taskStatus);
|
|
5987
6225
|
}
|
|
5988
6226
|
const successCount = tasks.filter((t) => t.status === "done").length + (completed - doneCount);
|
|
5989
6227
|
console.log(
|
|
5990
|
-
|
|
6228
|
+
import_chalk10.default.bold(
|
|
5991
6229
|
`
|
|
5992
|
-
${successCount === tasks.length ?
|
|
6230
|
+
${successCount === tasks.length ? import_chalk10.default.green("\u2714") : import_chalk10.default.yellow("!")} Incremental build: ${completed}/${tasks.length} tasks completed.`
|
|
5993
6231
|
)
|
|
5994
6232
|
);
|
|
5995
6233
|
}
|
|
5996
6234
|
// ── Mode: api ─────────────────────────────────────────────────────────────
|
|
5997
6235
|
async runApiMode(specFilePath, workingDir, context, options = {}) {
|
|
5998
6236
|
console.log(
|
|
5999
|
-
|
|
6237
|
+
import_chalk10.default.blue(
|
|
6000
6238
|
`
|
|
6001
6239
|
\u2500\u2500\u2500 Code Generation: API (${this.provider.providerName}/${this.provider.modelName}) \u2500\u2500\u2500`
|
|
6002
6240
|
)
|
|
6003
6241
|
);
|
|
6004
6242
|
const systemPrompt = getCodeGenSystemPrompt(options.repoType);
|
|
6005
6243
|
if (options.repoType && options.repoType !== "node-express" && options.repoType !== "node-koa" && options.repoType !== "unknown") {
|
|
6006
|
-
console.log(
|
|
6244
|
+
console.log(import_chalk10.default.gray(` Language: ${options.repoType} (using language-specific codegen prompt)`));
|
|
6007
6245
|
}
|
|
6008
6246
|
const spec = await fs10.readFile(specFilePath, "utf-8");
|
|
6009
|
-
|
|
6247
|
+
let constitutionSection = context?.constitution ? `
|
|
6010
6248
|
=== Project Constitution (MUST follow) ===
|
|
6011
6249
|
${context.constitution}
|
|
6012
6250
|
` : "";
|
|
@@ -6021,7 +6259,7 @@ ${buildDslContextSection(dsl)}
|
|
|
6021
6259
|
if (dsl) {
|
|
6022
6260
|
const cmpCount = dsl.components?.length ?? 0;
|
|
6023
6261
|
const cmpSuffix = cmpCount > 0 ? `, ${cmpCount} components` : "";
|
|
6024
|
-
console.log(
|
|
6262
|
+
console.log(import_chalk10.default.green(` \u2713 DSL loaded \u2014 ${dsl.endpoints.length} endpoints, ${dsl.models.length} models${cmpSuffix}`));
|
|
6025
6263
|
}
|
|
6026
6264
|
const isFrontend = isFrontendDeps(context?.dependencies ?? []);
|
|
6027
6265
|
let frontendSection = "";
|
|
@@ -6030,13 +6268,30 @@ ${buildDslContextSection(dsl)}
|
|
|
6030
6268
|
frontendSection = `
|
|
6031
6269
|
${buildFrontendContextSection(fctx)}
|
|
6032
6270
|
`;
|
|
6033
|
-
console.log(
|
|
6271
|
+
console.log(import_chalk10.default.gray(` Frontend context: ${fctx.framework} / ${fctx.httpClient} | hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
|
|
6272
|
+
}
|
|
6273
|
+
const allContextText = spec + constitutionSection + dslSection + frontendSection + installedPackagesSection + sharedConfigSection;
|
|
6274
|
+
const estimatedTokenCount = estimateTokens(allContextText);
|
|
6275
|
+
const budget = getDefaultBudget(this.provider.providerName);
|
|
6276
|
+
if (estimatedTokenCount > budget * 0.7) {
|
|
6277
|
+
console.log(
|
|
6278
|
+
import_chalk10.default.yellow(
|
|
6279
|
+
` \u26A0 Context size: ~${Math.round(estimatedTokenCount / 1e3)}K tokens (budget: ${Math.round(budget / 1e3)}K for ${this.provider.providerName})`
|
|
6280
|
+
)
|
|
6281
|
+
);
|
|
6282
|
+
if (constitutionSection.length > 4e3) {
|
|
6283
|
+
const s9Start = constitutionSection.indexOf("## 9.");
|
|
6284
|
+
if (s9Start > 0) {
|
|
6285
|
+
constitutionSection = constitutionSection.slice(0, s9Start) + "## 9. \u79EF\u7D2F\u6559\u8BAD (Accumulated Lessons)\n[Trimmed for context budget \u2014 run `ai-spec init --consolidate` to prune]\n";
|
|
6286
|
+
console.log(import_chalk10.default.gray(" \u2192 \xA79 trimmed from constitution to save tokens."));
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6034
6289
|
}
|
|
6035
6290
|
const tasks = await loadTasksForSpec(specFilePath);
|
|
6036
6291
|
if (tasks && tasks.length > 0) {
|
|
6037
6292
|
return this.runApiModeWithTasks(spec, tasks, specFilePath, workingDir, constitutionSection + dslSection + installedPackagesSection, frontendSection, sharedConfigSection, options, systemPrompt, context);
|
|
6038
6293
|
}
|
|
6039
|
-
console.log(
|
|
6294
|
+
console.log(import_chalk10.default.gray(" [1/2] Planning implementation files..."));
|
|
6040
6295
|
const planPrompt = `Based on the feature spec and project context below, list ALL files that need to be created or modified.
|
|
6041
6296
|
|
|
6042
6297
|
IMPORTANT: Check the "Existing Shared Config Files" section below FIRST. For any file listed there,
|
|
@@ -6059,18 +6314,18 @@ Output ONLY a valid JSON array:
|
|
|
6059
6314
|
const planResponse = await this.provider.generate(planPrompt, systemPrompt);
|
|
6060
6315
|
filePlan = parseJsonArray(planResponse);
|
|
6061
6316
|
} catch (err) {
|
|
6062
|
-
console.error(
|
|
6317
|
+
console.error(import_chalk10.default.red(" Failed to generate file plan:"), err);
|
|
6063
6318
|
}
|
|
6064
6319
|
if (filePlan.length === 0) {
|
|
6065
|
-
console.log(
|
|
6320
|
+
console.log(import_chalk10.default.yellow(" Could not determine file plan. Falling back to plan mode."));
|
|
6066
6321
|
await this.runPlanMode(specFilePath);
|
|
6067
6322
|
return [];
|
|
6068
6323
|
}
|
|
6069
|
-
console.log(
|
|
6324
|
+
console.log(import_chalk10.default.cyan(`
|
|
6070
6325
|
Plan: ${filePlan.length} file(s) to process`));
|
|
6071
6326
|
filePlan.forEach((item) => {
|
|
6072
|
-
const icon = item.action === "create" ?
|
|
6073
|
-
console.log(` ${icon} ${item.file}: ${
|
|
6327
|
+
const icon = item.action === "create" ? import_chalk10.default.green("+") : import_chalk10.default.yellow("~");
|
|
6328
|
+
console.log(` ${icon} ${item.file}: ${import_chalk10.default.gray(item.description)}`);
|
|
6074
6329
|
});
|
|
6075
6330
|
const { files } = await this.generateFiles(filePlan, spec, workingDir, constitutionSection + dslSection + frontendSection + installedPackagesSection, systemPrompt);
|
|
6076
6331
|
return files;
|
|
@@ -6079,13 +6334,13 @@ Output ONLY a valid JSON array:
|
|
|
6079
6334
|
const pendingTasks = tasks.filter((t) => t.status !== "done");
|
|
6080
6335
|
const doneCount = tasks.length - pendingTasks.length;
|
|
6081
6336
|
if (options.resume && doneCount > 0) {
|
|
6082
|
-
console.log(
|
|
6083
|
-
Task-based generation (resume): ${tasks.length} tasks (${
|
|
6337
|
+
console.log(import_chalk10.default.cyan(`
|
|
6338
|
+
Task-based generation (resume): ${tasks.length} tasks (${import_chalk10.default.green(doneCount + " already done")}, skipping)`));
|
|
6084
6339
|
} else if (doneCount > 0) {
|
|
6085
|
-
console.log(
|
|
6086
|
-
Task-based generation: ${tasks.length} tasks (${
|
|
6340
|
+
console.log(import_chalk10.default.cyan(`
|
|
6341
|
+
Task-based generation: ${tasks.length} tasks (${import_chalk10.default.green(doneCount + " already done")}, resuming from checkpoint)`));
|
|
6087
6342
|
} else {
|
|
6088
|
-
console.log(
|
|
6343
|
+
console.log(import_chalk10.default.cyan(`
|
|
6089
6344
|
Task-based generation: ${tasks.length} tasks`));
|
|
6090
6345
|
}
|
|
6091
6346
|
const sharedConfigPaths = new Set(
|
|
@@ -6117,9 +6372,9 @@ Output ONLY a valid JSON array:
|
|
|
6117
6372
|
const pct = Math.round(completedTasks / tasks.length * 100);
|
|
6118
6373
|
const barWidth = 20;
|
|
6119
6374
|
const filled = Math.round(pct / 100 * barWidth);
|
|
6120
|
-
const bar =
|
|
6375
|
+
const bar = import_chalk10.default.green("\u2588".repeat(filled)) + import_chalk10.default.gray("\u2591".repeat(barWidth - filled));
|
|
6121
6376
|
console.log(
|
|
6122
|
-
|
|
6377
|
+
import_chalk10.default.bold(`
|
|
6123
6378
|
[${bar}] ${pct}% \u26A1 Layer [${layer}] ${layerIcon} \u2014 ${layerTasks.length} tasks running in parallel`)
|
|
6124
6379
|
);
|
|
6125
6380
|
} else {
|
|
@@ -6127,7 +6382,7 @@ Output ONLY a valid JSON array:
|
|
|
6127
6382
|
}
|
|
6128
6383
|
const executeTask = async (task, batchIsParallel) => {
|
|
6129
6384
|
if (task.filesToTouch.length === 0) {
|
|
6130
|
-
if (!batchIsParallel) console.log(
|
|
6385
|
+
if (!batchIsParallel) console.log(import_chalk10.default.gray(" No files specified, skipping."));
|
|
6131
6386
|
return { task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false };
|
|
6132
6387
|
}
|
|
6133
6388
|
const filePlan = await Promise.all(
|
|
@@ -6184,13 +6439,19 @@ ${taskContext}`,
|
|
|
6184
6439
|
const layerResults = [];
|
|
6185
6440
|
for (const batch of taskBatches) {
|
|
6186
6441
|
const batchIsParallel = batch.length > 1;
|
|
6187
|
-
const batchResultPromises = batch.map(
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6442
|
+
const batchResultPromises = batch.map((task) => executeTask(task, batchIsParallel));
|
|
6443
|
+
const settled = await Promise.allSettled(batchResultPromises);
|
|
6444
|
+
const batchResults = [];
|
|
6445
|
+
for (let i = 0; i < settled.length; i++) {
|
|
6446
|
+
const outcome = settled[i];
|
|
6447
|
+
if (outcome.status === "fulfilled") {
|
|
6448
|
+
batchResults.push(outcome.value);
|
|
6449
|
+
} else {
|
|
6450
|
+
const task = batch[i];
|
|
6451
|
+
console.log(import_chalk10.default.yellow(` \u26A0 ${task.id} threw unexpectedly: ${outcome.reason?.message ?? outcome.reason}`));
|
|
6452
|
+
batchResults.push({ task, files: [], createdFiles: [], success: 0, total: 0, impliesRegistration: false });
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6194
6455
|
layerResults.push(...batchResults);
|
|
6195
6456
|
await updateCacheFromBatch(batchResults);
|
|
6196
6457
|
}
|
|
@@ -6202,14 +6463,14 @@ ${taskContext}`,
|
|
|
6202
6463
|
totalFiles += result.total;
|
|
6203
6464
|
allGeneratedFiles.push(...result.files);
|
|
6204
6465
|
if (isParallel) {
|
|
6205
|
-
const icon = result.success === result.total ?
|
|
6466
|
+
const icon = result.success === result.total ? import_chalk10.default.green("\u2714") : import_chalk10.default.yellow("!");
|
|
6206
6467
|
const layerTaskIcon = LAYER_ICONS[result.task.layer] ?? " ";
|
|
6207
6468
|
console.log(` ${icon} ${result.task.id} ${layerTaskIcon} ${result.task.title} \u2014 ${result.success}/${result.total} files`);
|
|
6208
6469
|
}
|
|
6209
6470
|
const taskStatus = result.success === result.total ? "done" : "failed";
|
|
6210
6471
|
await updateTaskStatus(specFilePath, result.task.id, taskStatus);
|
|
6211
6472
|
if (taskStatus === "failed") {
|
|
6212
|
-
console.log(
|
|
6473
|
+
console.log(import_chalk10.default.yellow(` \u26A0 ${result.task.id} marked as failed \u2014 re-run with --resume to retry`));
|
|
6213
6474
|
}
|
|
6214
6475
|
}
|
|
6215
6476
|
completedTasks += layerTasks.length;
|
|
@@ -6224,7 +6485,7 @@ ${taskContext}`,
|
|
|
6224
6485
|
if ((sharedFile.category === "route-index" || sharedFile.category === "store-index") && newModuleNames.length > 0) {
|
|
6225
6486
|
purpose = `Add to this file: import ${newModuleNames.join(", ")} from their respective paths and register them in the export/default array. Do NOT remove any existing imports.`;
|
|
6226
6487
|
}
|
|
6227
|
-
console.log(
|
|
6488
|
+
console.log(import_chalk10.default.gray(`
|
|
6228
6489
|
+ updating shared config: ${sharedFile.path} [${sharedFile.category}]`));
|
|
6229
6490
|
const updatedGeneratedFilesSection = buildGeneratedFilesSection(generatedFileCache);
|
|
6230
6491
|
await this.generateFiles(
|
|
@@ -6242,17 +6503,17 @@ Updating shared registration after layer [${layer}] completed. New modules: ${ne
|
|
|
6242
6503
|
}
|
|
6243
6504
|
}
|
|
6244
6505
|
console.log(
|
|
6245
|
-
|
|
6506
|
+
import_chalk10.default.bold(
|
|
6246
6507
|
`
|
|
6247
|
-
${totalSuccess === totalFiles ?
|
|
6508
|
+
${totalSuccess === totalFiles ? import_chalk10.default.green("\u2714") : import_chalk10.default.yellow("!")} Task-based generation: ${totalSuccess}/${totalFiles} files written across ${pendingTasks.length} tasks.`
|
|
6248
6509
|
)
|
|
6249
6510
|
);
|
|
6250
6511
|
return allGeneratedFiles;
|
|
6251
6512
|
}
|
|
6252
6513
|
async generateFiles(filePlan, spec, workingDir, constitutionSection, systemPrompt = getCodeGenSystemPrompt(), taskLabel) {
|
|
6253
|
-
const prefix = taskLabel ? ` [${
|
|
6514
|
+
const prefix = taskLabel ? ` [${import_chalk10.default.cyan(taskLabel)}] ` : " ";
|
|
6254
6515
|
if (!taskLabel) {
|
|
6255
|
-
console.log(
|
|
6516
|
+
console.log(import_chalk10.default.gray(`
|
|
6256
6517
|
Generating ${filePlan.length} file(s)...`));
|
|
6257
6518
|
}
|
|
6258
6519
|
let successCount = 0;
|
|
@@ -6273,6 +6534,7 @@ ${spec}
|
|
|
6273
6534
|
${constitutionSection}
|
|
6274
6535
|
=== ${existingContent ? "Existing content (modify and return the complete file)" : "Create this file from scratch"} ===
|
|
6275
6536
|
${existingContent || "Output only the complete file content."}`;
|
|
6537
|
+
const fileSpinner = startSpinner(`${prefix}Generating ${import_chalk10.default.bold(item.file)}...`);
|
|
6276
6538
|
try {
|
|
6277
6539
|
const raw = await this.provider.generate(codePrompt, systemPrompt);
|
|
6278
6540
|
const fileContent = stripCodeFences(raw);
|
|
@@ -6280,17 +6542,17 @@ ${existingContent || "Output only the complete file content."}`;
|
|
|
6280
6542
|
await fs10.ensureDir(path6.dirname(fullPath));
|
|
6281
6543
|
await fs10.writeFile(fullPath, fileContent, "utf-8");
|
|
6282
6544
|
getActiveLogger()?.fileWritten(item.file);
|
|
6283
|
-
|
|
6545
|
+
fileSpinner.succeed(`${existingContent ? import_chalk10.default.yellow("~") : import_chalk10.default.green("+")} ${import_chalk10.default.bold(item.file)}`);
|
|
6284
6546
|
successCount++;
|
|
6285
6547
|
writtenFiles.push(item.file);
|
|
6286
6548
|
} catch (err) {
|
|
6287
|
-
|
|
6549
|
+
fileSpinner.fail(`${import_chalk10.default.bold(item.file)} \u2014 ${err.message}`);
|
|
6288
6550
|
}
|
|
6289
6551
|
}
|
|
6290
6552
|
if (!taskLabel) {
|
|
6291
6553
|
console.log(
|
|
6292
|
-
|
|
6293
|
-
` ${successCount === filePlan.length ?
|
|
6554
|
+
import_chalk10.default.bold(
|
|
6555
|
+
` ${successCount === filePlan.length ? import_chalk10.default.green("\u2714") : import_chalk10.default.yellow("!")} ${successCount}/${filePlan.length} files written.`
|
|
6294
6556
|
)
|
|
6295
6557
|
);
|
|
6296
6558
|
}
|
|
@@ -6298,7 +6560,7 @@ ${existingContent || "Output only the complete file content."}`;
|
|
|
6298
6560
|
}
|
|
6299
6561
|
// ── Mode: plan ─────────────────────────────────────────────────────────────
|
|
6300
6562
|
async runPlanMode(specFilePath) {
|
|
6301
|
-
console.log(
|
|
6563
|
+
console.log(import_chalk10.default.blue("\n\u2500\u2500\u2500 Implementation Plan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6302
6564
|
const spec = await fs10.readFile(specFilePath, "utf-8");
|
|
6303
6565
|
const plan = await this.provider.generate(
|
|
6304
6566
|
`Create a detailed, step-by-step implementation plan for the following feature spec.
|
|
@@ -6311,80 +6573,18 @@ Be specific about:
|
|
|
6311
6573
|
${spec}`,
|
|
6312
6574
|
"You are a senior developer creating an actionable implementation guide."
|
|
6313
6575
|
);
|
|
6314
|
-
console.log(
|
|
6315
|
-
}
|
|
6316
|
-
};
|
|
6317
|
-
function topoSortLayerTasks(tasks) {
|
|
6318
|
-
if (tasks.length <= 1) return [tasks];
|
|
6319
|
-
const idSet = new Set(tasks.map((t) => t.id));
|
|
6320
|
-
const taskById = new Map(tasks.map((t) => [t.id, t]));
|
|
6321
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
6322
|
-
const dependents = /* @__PURE__ */ new Map();
|
|
6323
|
-
for (const task of tasks) {
|
|
6324
|
-
inDegree.set(task.id, 0);
|
|
6325
|
-
dependents.set(task.id, []);
|
|
6326
|
-
}
|
|
6327
|
-
for (const task of tasks) {
|
|
6328
|
-
const intraDeps = task.dependencies.filter((dep) => idSet.has(dep));
|
|
6329
|
-
inDegree.set(task.id, intraDeps.length);
|
|
6330
|
-
for (const dep of intraDeps) {
|
|
6331
|
-
dependents.get(dep).push(task.id);
|
|
6332
|
-
}
|
|
6576
|
+
console.log(import_chalk10.default.cyan("\n") + plan);
|
|
6333
6577
|
}
|
|
6334
|
-
const batches = [];
|
|
6335
|
-
const remaining = new Set(tasks.map((t) => t.id));
|
|
6336
|
-
while (remaining.size > 0) {
|
|
6337
|
-
const batch = [...remaining].filter((id) => inDegree.get(id) === 0).map((id) => taskById.get(id));
|
|
6338
|
-
if (batch.length === 0) {
|
|
6339
|
-
batches.push([...remaining].map((id) => taskById.get(id)));
|
|
6340
|
-
break;
|
|
6341
|
-
}
|
|
6342
|
-
batches.push(batch);
|
|
6343
|
-
for (const task of batch) {
|
|
6344
|
-
remaining.delete(task.id);
|
|
6345
|
-
for (const dependent of dependents.get(task.id)) {
|
|
6346
|
-
inDegree.set(dependent, inDegree.get(dependent) - 1);
|
|
6347
|
-
}
|
|
6348
|
-
}
|
|
6349
|
-
}
|
|
6350
|
-
return batches;
|
|
6351
|
-
}
|
|
6352
|
-
var LAYER_ICONS = {
|
|
6353
|
-
data: "\u{1F4BE}",
|
|
6354
|
-
infra: "\u2699\uFE0F ",
|
|
6355
|
-
service: "\u{1F527}",
|
|
6356
|
-
api: "\u{1F310}",
|
|
6357
|
-
view: "\u{1F5A5}\uFE0F ",
|
|
6358
|
-
route: "\u{1F5FA}\uFE0F ",
|
|
6359
|
-
test: "\u{1F9EA}"
|
|
6360
6578
|
};
|
|
6361
|
-
function printTaskProgress(completed, total, task, mode) {
|
|
6362
|
-
const pct = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
6363
|
-
const barWidth = 20;
|
|
6364
|
-
const filled = Math.round(pct / 100 * barWidth);
|
|
6365
|
-
const bar = import_chalk8.default.green("\u2588".repeat(filled)) + import_chalk8.default.gray("\u2591".repeat(barWidth - filled));
|
|
6366
|
-
const icon = LAYER_ICONS[task.layer] ?? " ";
|
|
6367
|
-
if (mode === "skip") {
|
|
6368
|
-
console.log(
|
|
6369
|
-
import_chalk8.default.gray(`
|
|
6370
|
-
[${bar}] ${pct}% \u2713 ${task.id} ${icon} ${task.title} \u2014 already done`)
|
|
6371
|
-
);
|
|
6372
|
-
} else {
|
|
6373
|
-
console.log(
|
|
6374
|
-
import_chalk8.default.bold(`
|
|
6375
|
-
[${bar}] ${pct}% \u2192 ${task.id} ${icon} ${task.title}`)
|
|
6376
|
-
);
|
|
6377
|
-
}
|
|
6378
|
-
}
|
|
6379
6579
|
|
|
6380
6580
|
// core/reviewer.ts
|
|
6381
|
-
var
|
|
6382
|
-
var
|
|
6581
|
+
var import_chalk12 = __toESM(require("chalk"));
|
|
6582
|
+
var import_child_process3 = require("child_process");
|
|
6383
6583
|
var path8 = __toESM(require("path"));
|
|
6384
6584
|
var fs12 = __toESM(require("fs-extra"));
|
|
6385
6585
|
|
|
6386
6586
|
// core/constitution-generator.ts
|
|
6387
|
-
var
|
|
6587
|
+
var import_chalk11 = __toESM(require("chalk"));
|
|
6388
6588
|
var fs11 = __toESM(require("fs-extra"));
|
|
6389
6589
|
var path7 = __toESM(require("path"));
|
|
6390
6590
|
|
|
@@ -6534,7 +6734,7 @@ async function loadConstitution(projectRoot) {
|
|
|
6534
6734
|
function printConstitutionHint(exists) {
|
|
6535
6735
|
if (!exists) {
|
|
6536
6736
|
console.log(
|
|
6537
|
-
|
|
6737
|
+
import_chalk11.default.yellow(
|
|
6538
6738
|
" \u26A1 Tip: Run `ai-spec init` to generate a Project Constitution for better spec quality."
|
|
6539
6739
|
)
|
|
6540
6740
|
);
|
|
@@ -6618,16 +6818,16 @@ var CodeReviewer = class {
|
|
|
6618
6818
|
this.projectRoot = projectRoot;
|
|
6619
6819
|
}
|
|
6620
6820
|
getGitDiff() {
|
|
6621
|
-
const silent = { encoding: "utf-8", stdio: "pipe" };
|
|
6821
|
+
const silent = { encoding: "utf-8", stdio: "pipe", cwd: this.projectRoot, timeout: 3e4 };
|
|
6622
6822
|
try {
|
|
6623
|
-
(0,
|
|
6823
|
+
(0, import_child_process3.execSync)("git rev-parse --is-inside-work-tree", silent);
|
|
6624
6824
|
} catch {
|
|
6625
6825
|
return "";
|
|
6626
6826
|
}
|
|
6627
6827
|
try {
|
|
6628
|
-
let diff = (0,
|
|
6629
|
-
if (!diff.trim()) diff = (0,
|
|
6630
|
-
if (!diff.trim()) diff = (0,
|
|
6828
|
+
let diff = (0, import_child_process3.execSync)("git diff --cached", silent);
|
|
6829
|
+
if (!diff.trim()) diff = (0, import_child_process3.execSync)("git diff HEAD", silent);
|
|
6830
|
+
if (!diff.trim()) diff = (0, import_child_process3.execSync)("git diff", silent);
|
|
6631
6831
|
return diff;
|
|
6632
6832
|
} catch {
|
|
6633
6833
|
return "";
|
|
@@ -6652,7 +6852,7 @@ var CodeReviewer = class {
|
|
|
6652
6852
|
async runThreePassReview(specContent, codeContext, specFile) {
|
|
6653
6853
|
let complianceReview = "";
|
|
6654
6854
|
if (specContent && specContent.trim() && specContent !== "(No spec \u2014 review for general code quality)") {
|
|
6655
|
-
console.log(
|
|
6855
|
+
console.log(import_chalk12.default.gray(" Pass 0/3: Spec compliance check..."));
|
|
6656
6856
|
const compliancePrompt = `Check whether the implementation covers every requirement in the spec.
|
|
6657
6857
|
|
|
6658
6858
|
=== Feature Spec ===
|
|
@@ -6664,13 +6864,13 @@ ${codeContext}`;
|
|
|
6664
6864
|
const complianceScore2 = extractComplianceScore(complianceReview);
|
|
6665
6865
|
const missingCount = extractMissingCount(complianceReview);
|
|
6666
6866
|
if (complianceScore2 > 0) {
|
|
6667
|
-
const scoreColor = complianceScore2 >= 8 ?
|
|
6867
|
+
const scoreColor = complianceScore2 >= 8 ? import_chalk12.default.green : complianceScore2 >= 6 ? import_chalk12.default.yellow : import_chalk12.default.red;
|
|
6668
6868
|
console.log(
|
|
6669
|
-
|
|
6869
|
+
import_chalk12.default.gray(" Pass 0 result: ") + scoreColor(`ComplianceScore ${complianceScore2}/10`) + (missingCount > 0 ? import_chalk12.default.red(` \xB7 ${missingCount} missing requirement(s)`) : import_chalk12.default.green(" \xB7 all requirements covered"))
|
|
6670
6870
|
);
|
|
6671
6871
|
}
|
|
6672
6872
|
}
|
|
6673
|
-
console.log(
|
|
6873
|
+
console.log(import_chalk12.default.gray(` Pass 1/3: Architecture review...`));
|
|
6674
6874
|
const accumulatedLessons = await loadAccumulatedLessons(this.projectRoot);
|
|
6675
6875
|
const archPrompt = `Review the architecture of this change.
|
|
6676
6876
|
${complianceReview ? `
|
|
@@ -6687,7 +6887,7 @@ ${specContent || "(No spec \u2014 review for general code quality)"}
|
|
|
6687
6887
|
=== Code ===
|
|
6688
6888
|
${codeContext}`;
|
|
6689
6889
|
const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
|
|
6690
|
-
console.log(
|
|
6890
|
+
console.log(import_chalk12.default.gray(" Pass 2/3: Implementation review..."));
|
|
6691
6891
|
const history = await loadReviewHistory(this.projectRoot);
|
|
6692
6892
|
const historyContext = buildHistoryContext(history);
|
|
6693
6893
|
const implPrompt = `Review the implementation details of this change.
|
|
@@ -6702,7 +6902,7 @@ ${codeContext}
|
|
|
6702
6902
|
${archReview}
|
|
6703
6903
|
${historyContext}`;
|
|
6704
6904
|
const implReview = await this.provider.generate(implPrompt, reviewImplementationSystemPrompt);
|
|
6705
|
-
console.log(
|
|
6905
|
+
console.log(import_chalk12.default.gray(" Pass 3/3: Impact & complexity assessment..."));
|
|
6706
6906
|
const impactPrompt = `Assess the impact and complexity of this change.
|
|
6707
6907
|
|
|
6708
6908
|
=== Feature Spec ===
|
|
@@ -6743,37 +6943,37 @@ ${sep}
|
|
|
6743
6943
|
return combined;
|
|
6744
6944
|
}
|
|
6745
6945
|
async reviewCode(specContent, specFile) {
|
|
6746
|
-
console.log(
|
|
6946
|
+
console.log(import_chalk12.default.cyan("\n\u2500\u2500\u2500 Automated Code Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6747
6947
|
const diff = this.getGitDiff();
|
|
6748
6948
|
if (!diff.trim()) {
|
|
6749
6949
|
console.log(
|
|
6750
|
-
|
|
6950
|
+
import_chalk12.default.yellow(" No git diff found. Stage or commit changes first, then run review.")
|
|
6751
6951
|
);
|
|
6752
|
-
console.log(
|
|
6952
|
+
console.log(import_chalk12.default.gray(" Tip: run `git add .` then `ai-spec review` to review your work."));
|
|
6753
6953
|
return "No changes";
|
|
6754
6954
|
}
|
|
6755
6955
|
const { files, added, removed } = this.getDiffStats(diff);
|
|
6756
6956
|
console.log(
|
|
6757
|
-
|
|
6957
|
+
import_chalk12.default.gray(` Diff: ${files} file(s), ${import_chalk12.default.green("+" + added)} ${import_chalk12.default.red("-" + removed)}`)
|
|
6758
6958
|
);
|
|
6759
6959
|
console.log(
|
|
6760
|
-
|
|
6960
|
+
import_chalk12.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
|
|
6761
6961
|
);
|
|
6762
6962
|
const codeContext = diff.slice(0, 1e4);
|
|
6763
6963
|
const reviewResult = await this.runThreePassReview(specContent, codeContext, specFile);
|
|
6764
|
-
console.log(
|
|
6964
|
+
console.log(import_chalk12.default.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6765
6965
|
console.log(reviewResult);
|
|
6766
|
-
console.log(
|
|
6966
|
+
console.log(import_chalk12.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
6767
6967
|
return reviewResult;
|
|
6768
6968
|
}
|
|
6769
6969
|
/**
|
|
6770
6970
|
* Review directly from generated file contents (for api mode where git diff is empty).
|
|
6771
6971
|
*/
|
|
6772
6972
|
async reviewFiles(specContent, filePaths, workingDir, specFile) {
|
|
6773
|
-
console.log(
|
|
6774
|
-
console.log(
|
|
6973
|
+
console.log(import_chalk12.default.cyan("\n\u2500\u2500\u2500 Automated Code Review (file-based) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6974
|
+
console.log(import_chalk12.default.gray(` Reviewing ${filePaths.length} generated file(s)...`));
|
|
6775
6975
|
console.log(
|
|
6776
|
-
|
|
6976
|
+
import_chalk12.default.blue(` Reviewing with ${this.provider.providerName}/${this.provider.modelName}...`)
|
|
6777
6977
|
);
|
|
6778
6978
|
let filesSection = "";
|
|
6779
6979
|
for (const filePath of filePaths) {
|
|
@@ -6794,28 +6994,28 @@ ${content.slice(0, 3e3)}`;
|
|
|
6794
6994
|
}
|
|
6795
6995
|
}
|
|
6796
6996
|
const reviewResult = await this.runThreePassReview(specContent, filesSection, specFile);
|
|
6797
|
-
console.log(
|
|
6997
|
+
console.log(import_chalk12.default.cyan("\n\u2500\u2500\u2500 Review Result \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6798
6998
|
console.log(reviewResult);
|
|
6799
|
-
console.log(
|
|
6999
|
+
console.log(import_chalk12.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
6800
7000
|
return reviewResult;
|
|
6801
7001
|
}
|
|
6802
7002
|
/** Print score trend from history (last N reviews) */
|
|
6803
7003
|
async printScoreTrend(limit = 5) {
|
|
6804
7004
|
const history = await loadReviewHistory(this.projectRoot);
|
|
6805
7005
|
if (history.length === 0) {
|
|
6806
|
-
console.log(
|
|
7006
|
+
console.log(import_chalk12.default.gray(" No review history yet."));
|
|
6807
7007
|
return;
|
|
6808
7008
|
}
|
|
6809
7009
|
const recent = history.slice(-limit);
|
|
6810
|
-
console.log(
|
|
7010
|
+
console.log(import_chalk12.default.cyan("\n\u2500\u2500\u2500 Review Score Trend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6811
7011
|
for (const entry of recent) {
|
|
6812
7012
|
const bar = "\u2588".repeat(entry.score) + "\u2591".repeat(10 - entry.score);
|
|
6813
|
-
const color = entry.score >= 8 ?
|
|
6814
|
-
const impactTag = entry.impactLevel ?
|
|
6815
|
-
const complexityTag = entry.complexityLevel ?
|
|
7013
|
+
const color = entry.score >= 8 ? import_chalk12.default.green : entry.score >= 6 ? import_chalk12.default.yellow : import_chalk12.default.red;
|
|
7014
|
+
const impactTag = entry.impactLevel ? import_chalk12.default.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? import_chalk12.default.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? import_chalk12.default.yellow(entry.impactLevel) : import_chalk12.default.green(entry.impactLevel)}`) : "";
|
|
7015
|
+
const complexityTag = entry.complexityLevel ? import_chalk12.default.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? import_chalk12.default.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? import_chalk12.default.yellow(entry.complexityLevel) : import_chalk12.default.green(entry.complexityLevel)}`) : "";
|
|
6816
7016
|
console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path8.basename(entry.specFile)}`);
|
|
6817
7017
|
}
|
|
6818
|
-
console.log(
|
|
7018
|
+
console.log(import_chalk12.default.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6819
7019
|
}
|
|
6820
7020
|
};
|
|
6821
7021
|
|
|
@@ -6865,17 +7065,17 @@ function parseSpecAndTasks(raw) {
|
|
|
6865
7065
|
}
|
|
6866
7066
|
|
|
6867
7067
|
// git/worktree.ts
|
|
6868
|
-
var
|
|
7068
|
+
var import_child_process4 = require("child_process");
|
|
6869
7069
|
var path9 = __toESM(require("path"));
|
|
6870
7070
|
var fs13 = __toESM(require("fs-extra"));
|
|
6871
|
-
var
|
|
7071
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
6872
7072
|
var GitWorktreeManager = class {
|
|
6873
7073
|
constructor(baseDir) {
|
|
6874
7074
|
this.baseDir = baseDir;
|
|
6875
7075
|
}
|
|
6876
7076
|
isGitRepo() {
|
|
6877
7077
|
try {
|
|
6878
|
-
(0,
|
|
7078
|
+
(0, import_child_process4.execSync)("git rev-parse --is-inside-work-tree", { cwd: this.baseDir, stdio: "ignore" });
|
|
6879
7079
|
return true;
|
|
6880
7080
|
} catch {
|
|
6881
7081
|
return false;
|
|
@@ -6899,58 +7099,58 @@ var GitWorktreeManager = class {
|
|
|
6899
7099
|
if (await fs13.pathExists(dest)) continue;
|
|
6900
7100
|
try {
|
|
6901
7101
|
await fs13.ensureSymlink(src, dest, "dir");
|
|
6902
|
-
console.log(
|
|
7102
|
+
console.log(import_chalk13.default.gray(` Symlinked ${dir}/ from base repo \u2192 worktree`));
|
|
6903
7103
|
} catch (err) {
|
|
6904
|
-
console.log(
|
|
6905
|
-
console.log(
|
|
7104
|
+
console.log(import_chalk13.default.yellow(` \u26A0 Could not symlink ${dir}/: ${err.message}`));
|
|
7105
|
+
console.log(import_chalk13.default.yellow(` Run \`npm install\` inside the worktree manually.`));
|
|
6906
7106
|
}
|
|
6907
7107
|
}
|
|
6908
7108
|
}
|
|
6909
7109
|
async createWorktree(idea) {
|
|
6910
7110
|
if (!this.isGitRepo()) {
|
|
6911
|
-
console.log(
|
|
7111
|
+
console.log(import_chalk13.default.yellow("\u26A0\uFE0F Not a git repository. Skipping worktree creation."));
|
|
6912
7112
|
return null;
|
|
6913
7113
|
}
|
|
6914
7114
|
const featureName = this.sanitizeFeatureName(idea);
|
|
6915
7115
|
const branchName = `feature/${featureName}`;
|
|
6916
7116
|
const repoName = path9.basename(this.baseDir);
|
|
6917
7117
|
const worktreePath = path9.resolve(this.baseDir, "..", `${repoName}-${featureName}`);
|
|
6918
|
-
console.log(
|
|
7118
|
+
console.log(import_chalk13.default.cyan(`
|
|
6919
7119
|
--- Setting up Git Worktree ---`));
|
|
6920
7120
|
if (await fs13.pathExists(worktreePath)) {
|
|
6921
|
-
console.log(
|
|
7121
|
+
console.log(import_chalk13.default.yellow(`\u26A0\uFE0F Worktree directory already exists at: ${worktreePath}`));
|
|
6922
7122
|
await this.linkDependencies(worktreePath);
|
|
6923
7123
|
return worktreePath;
|
|
6924
7124
|
}
|
|
6925
7125
|
try {
|
|
6926
7126
|
let branchExists = false;
|
|
6927
7127
|
try {
|
|
6928
|
-
(0,
|
|
7128
|
+
(0, import_child_process4.execSync)(`git show-ref --verify refs/heads/${branchName}`, {
|
|
6929
7129
|
cwd: this.baseDir,
|
|
6930
7130
|
stdio: "ignore"
|
|
6931
7131
|
});
|
|
6932
7132
|
branchExists = true;
|
|
6933
7133
|
} catch {
|
|
6934
7134
|
}
|
|
6935
|
-
console.log(
|
|
7135
|
+
console.log(import_chalk13.default.gray(`Creating worktree at: ${worktreePath}`));
|
|
6936
7136
|
if (branchExists) {
|
|
6937
|
-
(0,
|
|
7137
|
+
(0, import_child_process4.execSync)(`git worktree add "${worktreePath}" ${branchName}`, {
|
|
6938
7138
|
cwd: this.baseDir,
|
|
6939
7139
|
stdio: "inherit"
|
|
6940
7140
|
});
|
|
6941
7141
|
} else {
|
|
6942
|
-
(0,
|
|
7142
|
+
(0, import_child_process4.execSync)(`git worktree add -b ${branchName} "${worktreePath}"`, {
|
|
6943
7143
|
cwd: this.baseDir,
|
|
6944
7144
|
stdio: "inherit"
|
|
6945
7145
|
});
|
|
6946
7146
|
}
|
|
6947
7147
|
console.log(
|
|
6948
|
-
|
|
7148
|
+
import_chalk13.default.green(`\u2714 Worktree successfully created and isolated on branch '${branchName}'`)
|
|
6949
7149
|
);
|
|
6950
7150
|
await this.linkDependencies(worktreePath);
|
|
6951
7151
|
return worktreePath;
|
|
6952
7152
|
} catch (error) {
|
|
6953
|
-
console.error(
|
|
7153
|
+
console.error(import_chalk13.default.red("Failed to create git worktree:"), error);
|
|
6954
7154
|
return null;
|
|
6955
7155
|
}
|
|
6956
7156
|
}
|