ai-spec-dev 0.30.1 → 0.31.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/README.md +29 -1
- package/RELEASE_LOG.md +33 -0
- package/cli/index.ts +24 -1
- package/core/prompt-hasher.ts +42 -0
- package/core/run-logger.ts +21 -0
- package/core/self-evaluator.ts +172 -0
- package/dist/cli/index.js +444 -323
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +444 -323
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/purpose.md +189 -2
package/dist/cli/index.js
CHANGED
|
@@ -490,7 +490,7 @@ var init_workspace_loader = __esm({
|
|
|
490
490
|
var import_commander = require("commander");
|
|
491
491
|
var path22 = __toESM(require("path"));
|
|
492
492
|
var fs23 = __toESM(require("fs-extra"));
|
|
493
|
-
var
|
|
493
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
494
494
|
var dotenv = __toESM(require("dotenv"));
|
|
495
495
|
var import_prompts3 = require("@inquirer/prompts");
|
|
496
496
|
|
|
@@ -6127,6 +6127,16 @@ var RunLogger = class {
|
|
|
6127
6127
|
this.log.errors.push(`[${event}] ${error}`);
|
|
6128
6128
|
this.flush();
|
|
6129
6129
|
}
|
|
6130
|
+
/** Record the prompt hash for this run (call once at run start). */
|
|
6131
|
+
setPromptHash(hash) {
|
|
6132
|
+
this.log.promptHash = hash;
|
|
6133
|
+
this.flush();
|
|
6134
|
+
}
|
|
6135
|
+
/** Record the harness self-eval score (call once at run end). */
|
|
6136
|
+
setHarnessScore(score) {
|
|
6137
|
+
this.log.harnessScore = score;
|
|
6138
|
+
this.flush();
|
|
6139
|
+
}
|
|
6130
6140
|
fileWritten(filePath) {
|
|
6131
6141
|
if (!this.log.filesWritten.includes(filePath)) {
|
|
6132
6142
|
this.log.filesWritten.push(filePath);
|
|
@@ -9911,6 +9921,103 @@ async function exportOpenApi(dsl, projectDir, opts = {}) {
|
|
|
9911
9921
|
return outputPath;
|
|
9912
9922
|
}
|
|
9913
9923
|
|
|
9924
|
+
// core/prompt-hasher.ts
|
|
9925
|
+
var import_crypto = require("crypto");
|
|
9926
|
+
init_codegen_prompt();
|
|
9927
|
+
init_codegen_prompt();
|
|
9928
|
+
function computePromptHash() {
|
|
9929
|
+
const segments = [
|
|
9930
|
+
codeGenSystemPrompt,
|
|
9931
|
+
dslSystemPrompt,
|
|
9932
|
+
specPrompt,
|
|
9933
|
+
reviewArchitectureSystemPrompt,
|
|
9934
|
+
reviewImplementationSystemPrompt,
|
|
9935
|
+
reviewImpactComplexitySystemPrompt
|
|
9936
|
+
];
|
|
9937
|
+
return (0, import_crypto.createHash)("sha256").update(segments.join("\0")).digest("hex").slice(0, 8);
|
|
9938
|
+
}
|
|
9939
|
+
|
|
9940
|
+
// core/self-evaluator.ts
|
|
9941
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
9942
|
+
var ENDPOINT_LAYER_PATTERNS = [
|
|
9943
|
+
/src\/api/,
|
|
9944
|
+
/src\/routes?/,
|
|
9945
|
+
/src\/controller/,
|
|
9946
|
+
/src\/handler/,
|
|
9947
|
+
/src\/endpoints?/
|
|
9948
|
+
];
|
|
9949
|
+
var MODEL_LAYER_PATTERNS = [
|
|
9950
|
+
/src\/model/,
|
|
9951
|
+
/src\/schema/,
|
|
9952
|
+
/src\/entit/,
|
|
9953
|
+
/src\/db/,
|
|
9954
|
+
/prisma/,
|
|
9955
|
+
/src\/data/,
|
|
9956
|
+
/src\/domain/
|
|
9957
|
+
];
|
|
9958
|
+
function extractReviewScore(reviewText) {
|
|
9959
|
+
const match = reviewText.match(/Score:\s*(\d+(?:\.\d+)?)\s*\/\s*10/i);
|
|
9960
|
+
return match ? parseFloat(match[1]) : null;
|
|
9961
|
+
}
|
|
9962
|
+
function runSelfEval(opts) {
|
|
9963
|
+
const { dsl, generatedFiles, compilePassed, reviewText, promptHash, logger } = opts;
|
|
9964
|
+
const endpointsTotal = dsl?.endpoints?.length ?? 0;
|
|
9965
|
+
const modelsTotal = dsl?.models?.length ?? 0;
|
|
9966
|
+
const endpointLayerCovered = generatedFiles.some(
|
|
9967
|
+
(f) => ENDPOINT_LAYER_PATTERNS.some((p) => p.test(f))
|
|
9968
|
+
);
|
|
9969
|
+
const modelLayerCovered = generatedFiles.some(
|
|
9970
|
+
(f) => MODEL_LAYER_PATTERNS.some((p) => p.test(f))
|
|
9971
|
+
);
|
|
9972
|
+
let dslCoverageScore = 10;
|
|
9973
|
+
if (generatedFiles.length === 0) {
|
|
9974
|
+
dslCoverageScore = 0;
|
|
9975
|
+
} else {
|
|
9976
|
+
if (endpointsTotal > 0 && !endpointLayerCovered) dslCoverageScore -= 4;
|
|
9977
|
+
if (modelsTotal > 0 && !modelLayerCovered) dslCoverageScore -= 3;
|
|
9978
|
+
}
|
|
9979
|
+
const compileScore = compilePassed ? 10 : 5;
|
|
9980
|
+
const reviewScore = reviewText ? extractReviewScore(reviewText) : null;
|
|
9981
|
+
const harnessScore = reviewScore !== null ? Math.round((dslCoverageScore * 0.4 + compileScore * 0.3 + reviewScore * 0.3) * 10) / 10 : Math.round((dslCoverageScore * 0.55 + compileScore * 0.45) * 10) / 10;
|
|
9982
|
+
const result = {
|
|
9983
|
+
dslCoverageScore,
|
|
9984
|
+
compileScore,
|
|
9985
|
+
reviewScore,
|
|
9986
|
+
harnessScore,
|
|
9987
|
+
promptHash,
|
|
9988
|
+
detail: {
|
|
9989
|
+
endpointsTotal,
|
|
9990
|
+
endpointLayerCovered,
|
|
9991
|
+
modelsTotal,
|
|
9992
|
+
modelLayerCovered,
|
|
9993
|
+
filesWritten: generatedFiles.length
|
|
9994
|
+
}
|
|
9995
|
+
};
|
|
9996
|
+
logger.setHarnessScore(harnessScore);
|
|
9997
|
+
logger.stageEnd("self_eval", {
|
|
9998
|
+
harnessScore,
|
|
9999
|
+
dslCoverageScore,
|
|
10000
|
+
compileScore,
|
|
10001
|
+
reviewScore: reviewScore ?? void 0,
|
|
10002
|
+
promptHash
|
|
10003
|
+
});
|
|
10004
|
+
return result;
|
|
10005
|
+
}
|
|
10006
|
+
function printSelfEval(result) {
|
|
10007
|
+
const scoreColor = result.harnessScore >= 8 ? import_chalk18.default.green : result.harnessScore >= 6 ? import_chalk18.default.yellow : import_chalk18.default.red;
|
|
10008
|
+
const filled = Math.round(result.harnessScore);
|
|
10009
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
|
|
10010
|
+
const compileTag = result.compileScore === 10 ? import_chalk18.default.green("pass") : import_chalk18.default.yellow("partial");
|
|
10011
|
+
const reviewTag = result.reviewScore !== null ? `Review: ${result.reviewScore}/10` : import_chalk18.default.gray("Review: skipped");
|
|
10012
|
+
console.log(import_chalk18.default.cyan("\n\u2500\u2500\u2500 Harness Self-Eval \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"));
|
|
10013
|
+
console.log(` Score : ${scoreColor(`[${bar}] ${result.harnessScore}/10`)}`);
|
|
10014
|
+
console.log(
|
|
10015
|
+
` DSL : ${scoreColor(result.dslCoverageScore + "/10")} Compile: ${compileTag} ${reviewTag}`
|
|
10016
|
+
);
|
|
10017
|
+
console.log(import_chalk18.default.gray(` Prompt : ${result.promptHash}`));
|
|
10018
|
+
console.log(import_chalk18.default.gray("\u2500".repeat(49)));
|
|
10019
|
+
}
|
|
10020
|
+
|
|
9914
10021
|
// cli/index.ts
|
|
9915
10022
|
dotenv.config();
|
|
9916
10023
|
var CONFIG_FILE = ".ai-spec.json";
|
|
@@ -9942,20 +10049,20 @@ async function resolveApiKey(providerName, cliKey) {
|
|
|
9942
10049
|
validate: (v2) => v2.trim().length > 0 || "API key cannot be empty"
|
|
9943
10050
|
});
|
|
9944
10051
|
await saveKey(providerName, newKey.trim());
|
|
9945
|
-
console.log(
|
|
10052
|
+
console.log(import_chalk19.default.gray(` Key saved to ${KEY_STORE_FILE}`));
|
|
9946
10053
|
return newKey.trim();
|
|
9947
10054
|
}
|
|
9948
10055
|
function printBanner(opts) {
|
|
9949
|
-
console.log(
|
|
9950
|
-
console.log(
|
|
9951
|
-
console.log(
|
|
9952
|
-
console.log(
|
|
10056
|
+
console.log(import_chalk19.default.blue("\n" + "\u2500".repeat(52)));
|
|
10057
|
+
console.log(import_chalk19.default.bold(" ai-spec \u2014 AI-driven Development Orchestrator"));
|
|
10058
|
+
console.log(import_chalk19.default.blue("\u2500".repeat(52)));
|
|
10059
|
+
console.log(import_chalk19.default.gray(` Spec : ${opts.specProvider} / ${opts.specModel}`));
|
|
9953
10060
|
console.log(
|
|
9954
|
-
|
|
10061
|
+
import_chalk19.default.gray(
|
|
9955
10062
|
` Codegen : ${opts.codegenMode} (${opts.codegenProvider} / ${opts.codegenModel})`
|
|
9956
10063
|
)
|
|
9957
10064
|
);
|
|
9958
|
-
console.log(
|
|
10065
|
+
console.log(import_chalk19.default.blue("\u2500".repeat(52) + "\n"));
|
|
9959
10066
|
}
|
|
9960
10067
|
var program = new import_commander.Command();
|
|
9961
10068
|
program.name("ai-spec").description("AI-driven Development Orchestrator \u2014 spec, generate, review").version("0.14.1");
|
|
@@ -9982,42 +10089,42 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
9982
10089
|
const workspaceLoader = new WorkspaceLoader(currentDir);
|
|
9983
10090
|
const workspaceConfig = await workspaceLoader.load();
|
|
9984
10091
|
if (workspaceConfig) {
|
|
9985
|
-
console.log(
|
|
10092
|
+
console.log(import_chalk19.default.cyan(`
|
|
9986
10093
|
[Workspace] Detected workspace: ${workspaceConfig.name}`));
|
|
9987
|
-
console.log(
|
|
10094
|
+
console.log(import_chalk19.default.gray(` Repos: ${workspaceConfig.repos.map((r) => r.name).join(", ")}`));
|
|
9988
10095
|
const pipelineResults = await runMultiRepoPipeline(idea, workspaceConfig, opts, currentDir, config2);
|
|
9989
10096
|
if (opts.serve) {
|
|
9990
|
-
console.log(
|
|
10097
|
+
console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Auto-serve: starting mock server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
9991
10098
|
const backendResult = pipelineResults.find((r) => r.role === "backend" && r.status === "success" && r.dsl);
|
|
9992
10099
|
const frontendResult = pipelineResults.find((r) => (r.role === "frontend" || r.role === "mobile") && r.status === "success");
|
|
9993
10100
|
if (!backendResult) {
|
|
9994
|
-
console.log(
|
|
10101
|
+
console.log(import_chalk19.default.yellow(" No successful backend with DSL found \u2014 skipping auto-serve."));
|
|
9995
10102
|
} else {
|
|
9996
10103
|
const mockPort = 3001;
|
|
9997
10104
|
const mockResult = await generateMockAssets(backendResult.dsl, backendResult.repoAbsPath, { port: mockPort });
|
|
9998
10105
|
const serverJsPath = path22.join(backendResult.repoAbsPath, "mock", "server.js");
|
|
9999
|
-
console.log(
|
|
10106
|
+
console.log(import_chalk19.default.green(` \u2714 Mock assets generated (${mockResult.files.length} file(s))`));
|
|
10000
10107
|
const pid = startMockServerBackground(serverJsPath, mockPort);
|
|
10001
|
-
console.log(
|
|
10108
|
+
console.log(import_chalk19.default.green(` \u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${mockPort}`));
|
|
10002
10109
|
if (frontendResult) {
|
|
10003
10110
|
const proxyResult = await applyMockProxy(frontendResult.repoAbsPath, mockPort, backendResult.dsl.endpoints);
|
|
10004
10111
|
await saveMockServerPid(frontendResult.repoAbsPath, pid);
|
|
10005
10112
|
if (proxyResult.applied) {
|
|
10006
|
-
console.log(
|
|
10007
|
-
console.log(
|
|
10113
|
+
console.log(import_chalk19.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
|
|
10114
|
+
console.log(import_chalk19.default.bold.cyan(`
|
|
10008
10115
|
Ready! Run your frontend dev server:`));
|
|
10009
|
-
console.log(
|
|
10010
|
-
console.log(
|
|
10011
|
-
console.log(
|
|
10116
|
+
console.log(import_chalk19.default.white(` cd ${frontendResult.repoAbsPath}`));
|
|
10117
|
+
console.log(import_chalk19.default.white(` ${proxyResult.devCommand}`));
|
|
10118
|
+
console.log(import_chalk19.default.gray(`
|
|
10012
10119
|
When done, restore: ai-spec mock --restore --frontend ${frontendResult.repoAbsPath}`));
|
|
10013
10120
|
} else {
|
|
10014
|
-
console.log(
|
|
10015
|
-
if (proxyResult.note) console.log(
|
|
10016
|
-
console.log(
|
|
10121
|
+
console.log(import_chalk19.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
|
|
10122
|
+
if (proxyResult.note) console.log(import_chalk19.default.gray(` ${proxyResult.note}`));
|
|
10123
|
+
console.log(import_chalk19.default.gray(` Mock server: http://localhost:${mockPort}`));
|
|
10017
10124
|
}
|
|
10018
10125
|
} else {
|
|
10019
|
-
console.log(
|
|
10020
|
-
console.log(
|
|
10126
|
+
console.log(import_chalk19.default.gray(` No frontend repo found \u2014 mock server is running at http://localhost:${mockPort}`));
|
|
10127
|
+
console.log(import_chalk19.default.gray(` Configure your frontend proxy manually to point to http://localhost:${mockPort}`));
|
|
10021
10128
|
}
|
|
10022
10129
|
}
|
|
10023
10130
|
}
|
|
@@ -10038,7 +10145,7 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10038
10145
|
codegenModel: codegenModelName
|
|
10039
10146
|
});
|
|
10040
10147
|
const runId = generateRunId();
|
|
10041
|
-
console.log(
|
|
10148
|
+
console.log(import_chalk19.default.gray(` Run ID: ${runId}`));
|
|
10042
10149
|
const runSnapshot = new RunSnapshot(currentDir, runId);
|
|
10043
10150
|
setActiveSnapshot(runSnapshot);
|
|
10044
10151
|
const runLogger = new RunLogger(currentDir, runId, {
|
|
@@ -10046,25 +10153,27 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10046
10153
|
model: specModelName
|
|
10047
10154
|
});
|
|
10048
10155
|
setActiveLogger(runLogger);
|
|
10049
|
-
|
|
10156
|
+
const promptHash = computePromptHash();
|
|
10157
|
+
runLogger.setPromptHash(promptHash);
|
|
10158
|
+
console.log(import_chalk19.default.blue("[1/6] Loading project context..."));
|
|
10050
10159
|
runLogger.stageStart("context_load");
|
|
10051
10160
|
const loader = new ContextLoader(currentDir);
|
|
10052
10161
|
const context = await loader.loadProjectContext();
|
|
10053
10162
|
const { type: detectedRepoType } = await detectRepoType(currentDir);
|
|
10054
10163
|
runLogger.stageEnd("context_load", { techStack: context.techStack, repoType: detectedRepoType });
|
|
10055
|
-
console.log(
|
|
10056
|
-
console.log(
|
|
10057
|
-
console.log(
|
|
10164
|
+
console.log(import_chalk19.default.gray(` Tech stack : ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
|
|
10165
|
+
console.log(import_chalk19.default.gray(` Dependencies: ${context.dependencies.length} packages`));
|
|
10166
|
+
console.log(import_chalk19.default.gray(` API files : ${context.apiStructure.length} files`));
|
|
10058
10167
|
if (context.schema) {
|
|
10059
|
-
console.log(
|
|
10168
|
+
console.log(import_chalk19.default.gray(` Prisma schema: found`));
|
|
10060
10169
|
}
|
|
10061
10170
|
if (context.constitution) {
|
|
10062
|
-
console.log(
|
|
10171
|
+
console.log(import_chalk19.default.green(` Constitution : found (.ai-spec-constitution.md)`));
|
|
10063
10172
|
if (context.constitution.length > 6e3) {
|
|
10064
|
-
console.log(
|
|
10173
|
+
console.log(import_chalk19.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
|
|
10065
10174
|
}
|
|
10066
10175
|
} else {
|
|
10067
|
-
console.log(
|
|
10176
|
+
console.log(import_chalk19.default.yellow(" Constitution : not found \u2014 auto-generating..."));
|
|
10068
10177
|
try {
|
|
10069
10178
|
const constitutionGen = new ConstitutionGenerator(
|
|
10070
10179
|
createProvider(specProviderName, specApiKey, specModelName)
|
|
@@ -10072,12 +10181,12 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10072
10181
|
const constitutionContent = await constitutionGen.generate(currentDir);
|
|
10073
10182
|
await constitutionGen.saveConstitution(currentDir, constitutionContent);
|
|
10074
10183
|
context.constitution = constitutionContent;
|
|
10075
|
-
console.log(
|
|
10184
|
+
console.log(import_chalk19.default.green(` Constitution : \u2714 generated and saved (.ai-spec-constitution.md)`));
|
|
10076
10185
|
} catch (err) {
|
|
10077
|
-
console.log(
|
|
10186
|
+
console.log(import_chalk19.default.yellow(` Constitution : \u26A0 auto-generation failed (${err.message}), continuing without it.`));
|
|
10078
10187
|
}
|
|
10079
10188
|
}
|
|
10080
|
-
console.log(
|
|
10189
|
+
console.log(import_chalk19.default.blue(`
|
|
10081
10190
|
[2/6] Generating spec with ${specProviderName}/${specModelName}...`));
|
|
10082
10191
|
const specProvider = createProvider(specProviderName, specApiKey, specModelName);
|
|
10083
10192
|
let initialSpec;
|
|
@@ -10087,30 +10196,30 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10087
10196
|
if (opts.skipTasks) {
|
|
10088
10197
|
const generator = new SpecGenerator(specProvider);
|
|
10089
10198
|
initialSpec = await generator.generateSpec(idea, context);
|
|
10090
|
-
console.log(
|
|
10199
|
+
console.log(import_chalk19.default.green(" \u2714 Spec generated."));
|
|
10091
10200
|
} else {
|
|
10092
10201
|
const result = await generateSpecWithTasks(specProvider, idea, context);
|
|
10093
10202
|
initialSpec = result.spec;
|
|
10094
10203
|
initialTasks = result.tasks;
|
|
10095
|
-
console.log(
|
|
10204
|
+
console.log(import_chalk19.default.green(` \u2714 Spec generated.`));
|
|
10096
10205
|
if (initialTasks.length > 0) {
|
|
10097
|
-
console.log(
|
|
10206
|
+
console.log(import_chalk19.default.green(` \u2714 ${initialTasks.length} tasks generated (combined call).`));
|
|
10098
10207
|
} else {
|
|
10099
|
-
console.log(
|
|
10208
|
+
console.log(import_chalk19.default.yellow(" \u26A0 Tasks not parsed from response \u2014 will retry separately after refinement."));
|
|
10100
10209
|
}
|
|
10101
10210
|
}
|
|
10102
10211
|
runLogger.stageEnd("spec_gen", { taskCount: initialTasks.length });
|
|
10103
10212
|
} catch (err) {
|
|
10104
10213
|
runLogger.stageFail("spec_gen", err.message);
|
|
10105
|
-
console.error(
|
|
10214
|
+
console.error(import_chalk19.default.red(" \u2718 Spec generation failed:"), err);
|
|
10106
10215
|
process.exit(1);
|
|
10107
10216
|
}
|
|
10108
10217
|
let finalSpec;
|
|
10109
10218
|
if (opts.fast) {
|
|
10110
|
-
console.log(
|
|
10219
|
+
console.log(import_chalk19.default.gray("\n[3/6] Skipping refinement (--fast)."));
|
|
10111
10220
|
finalSpec = initialSpec;
|
|
10112
10221
|
} else {
|
|
10113
|
-
console.log(
|
|
10222
|
+
console.log(import_chalk19.default.blue("\n[3/6] Interactive spec refinement..."));
|
|
10114
10223
|
runLogger.stageStart("spec_refine");
|
|
10115
10224
|
const refiner = new SpecRefiner(specProvider);
|
|
10116
10225
|
finalSpec = await refiner.refineLoop(initialSpec);
|
|
@@ -10121,7 +10230,7 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10121
10230
|
const shouldRunAssessment = !opts.skipAssessment && (!opts.auto || minScore > 0);
|
|
10122
10231
|
if (shouldRunAssessment) {
|
|
10123
10232
|
if (!opts.auto) {
|
|
10124
|
-
console.log(
|
|
10233
|
+
console.log(import_chalk19.default.blue("\n[3.4/6] Spec quality assessment..."));
|
|
10125
10234
|
}
|
|
10126
10235
|
runLogger.stageStart("spec_assess");
|
|
10127
10236
|
const assessment = await assessSpec(specProvider, finalSpec, context.constitution ?? void 0);
|
|
@@ -10130,45 +10239,45 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10130
10239
|
if (!opts.auto) printSpecAssessment(assessment);
|
|
10131
10240
|
if (minScore > 0 && assessment.overallScore < minScore) {
|
|
10132
10241
|
if (opts.force) {
|
|
10133
|
-
console.log(
|
|
10242
|
+
console.log(import_chalk19.default.yellow(`
|
|
10134
10243
|
\u26A0 Score gate: ${assessment.overallScore}/10 < minimum ${minScore}/10 \u2014 bypassed with --force.`));
|
|
10135
10244
|
} else {
|
|
10136
10245
|
runLogger.stageFail("spec_assess", `Score gate: ${assessment.overallScore} < ${minScore}`);
|
|
10137
|
-
console.log(
|
|
10246
|
+
console.log(import_chalk19.default.red(`
|
|
10138
10247
|
\u2718 Spec quality gate failed: overallScore ${assessment.overallScore}/10 < minimum ${minScore}/10`));
|
|
10139
10248
|
if (!opts.auto) {
|
|
10140
|
-
console.log(
|
|
10249
|
+
console.log(import_chalk19.default.gray(` Address the issues above and re-run, or use --force to bypass.`));
|
|
10141
10250
|
} else {
|
|
10142
|
-
console.log(
|
|
10251
|
+
console.log(import_chalk19.default.gray(` Auto mode: gate enforced. Fix the spec or lower minSpecScore, or use --force to bypass.`));
|
|
10143
10252
|
}
|
|
10144
|
-
console.log(
|
|
10253
|
+
console.log(import_chalk19.default.gray(` Gate threshold set in .ai-spec.json \u2192 "minSpecScore": ${minScore}`));
|
|
10145
10254
|
process.exit(1);
|
|
10146
10255
|
}
|
|
10147
10256
|
}
|
|
10148
10257
|
} else {
|
|
10149
10258
|
runLogger.stageEnd("spec_assess", { skipped: true });
|
|
10150
10259
|
if (!opts.auto) {
|
|
10151
|
-
console.log(
|
|
10260
|
+
console.log(import_chalk19.default.gray(" (Assessment skipped \u2014 AI call failed or timed out)"));
|
|
10152
10261
|
}
|
|
10153
10262
|
}
|
|
10154
10263
|
}
|
|
10155
10264
|
if (!opts.auto) {
|
|
10156
|
-
console.log(
|
|
10265
|
+
console.log(import_chalk19.default.blue("\n[3.5/6] Approval Gate \u2014 review before code generation"));
|
|
10157
10266
|
const specLines = finalSpec.split("\n").length;
|
|
10158
10267
|
const specWords = finalSpec.split(/\s+/).length;
|
|
10159
10268
|
const taskCountHint = initialTasks.length > 0 ? ` Tasks generated : ${initialTasks.length}` : "";
|
|
10160
|
-
console.log(
|
|
10161
|
-
if (taskCountHint) console.log(
|
|
10269
|
+
console.log(import_chalk19.default.gray(` Spec length : ${specLines} lines / ${specWords} words`));
|
|
10270
|
+
if (taskCountHint) console.log(import_chalk19.default.gray(taskCountHint));
|
|
10162
10271
|
const previewSpecsDir = path22.join(currentDir, "specs");
|
|
10163
10272
|
const slug = featureSlug;
|
|
10164
10273
|
const prevVersion = await findLatestVersion(previewSpecsDir, slug);
|
|
10165
10274
|
if (prevVersion) {
|
|
10166
|
-
console.log(
|
|
10275
|
+
console.log(import_chalk19.default.gray(` Previous version: v${prevVersion.version} (${prevVersion.filePath})`));
|
|
10167
10276
|
const diff = computeDiff(prevVersion.content, finalSpec);
|
|
10168
|
-
console.log(
|
|
10277
|
+
console.log(import_chalk19.default.cyan("\n \u2500\u2500 Changes vs previous version \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10169
10278
|
printDiffSummary(diff, `v${prevVersion.version} \u2192 v${prevVersion.version + 1}`);
|
|
10170
10279
|
printDiff(diff);
|
|
10171
|
-
console.log(
|
|
10280
|
+
console.log(import_chalk19.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"));
|
|
10172
10281
|
}
|
|
10173
10282
|
const gate = await (0, import_prompts3.select)({
|
|
10174
10283
|
message: "Ready to proceed to code generation?",
|
|
@@ -10179,9 +10288,9 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10179
10288
|
]
|
|
10180
10289
|
});
|
|
10181
10290
|
if (gate === "view") {
|
|
10182
|
-
console.log(
|
|
10291
|
+
console.log(import_chalk19.default.cyan("\n" + "\u2500".repeat(52)));
|
|
10183
10292
|
console.log(finalSpec);
|
|
10184
|
-
console.log(
|
|
10293
|
+
console.log(import_chalk19.default.cyan("\u2500".repeat(52) + "\n"));
|
|
10185
10294
|
const confirm22 = await (0, import_prompts3.select)({
|
|
10186
10295
|
message: "Proceed to code generation?",
|
|
10187
10296
|
choices: [
|
|
@@ -10190,92 +10299,92 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10190
10299
|
]
|
|
10191
10300
|
});
|
|
10192
10301
|
if (confirm22 === "abort") {
|
|
10193
|
-
console.log(
|
|
10302
|
+
console.log(import_chalk19.default.yellow(" Aborted. Spec was NOT saved."));
|
|
10194
10303
|
process.exit(0);
|
|
10195
10304
|
}
|
|
10196
10305
|
} else if (gate === "abort") {
|
|
10197
|
-
console.log(
|
|
10306
|
+
console.log(import_chalk19.default.yellow(" Aborted. Spec was NOT saved."));
|
|
10198
10307
|
process.exit(0);
|
|
10199
10308
|
}
|
|
10200
|
-
console.log(
|
|
10309
|
+
console.log(import_chalk19.default.green(" \u2714 Approved \u2014 continuing to code generation."));
|
|
10201
10310
|
} else {
|
|
10202
|
-
console.log(
|
|
10311
|
+
console.log(import_chalk19.default.gray("[3.5/6] Approval Gate: skipped (--auto)."));
|
|
10203
10312
|
}
|
|
10204
10313
|
let extractedDsl = null;
|
|
10205
10314
|
if (opts.skipDsl) {
|
|
10206
|
-
console.log(
|
|
10315
|
+
console.log(import_chalk19.default.gray("\n[DSL] Skipped (--skip-dsl)."));
|
|
10207
10316
|
} else {
|
|
10208
|
-
console.log(
|
|
10209
|
-
console.log(
|
|
10317
|
+
console.log(import_chalk19.default.blue("\n[DSL] Extracting structured DSL from spec..."));
|
|
10318
|
+
console.log(import_chalk19.default.gray(` Provider: ${specProviderName}/${specModelName}`));
|
|
10210
10319
|
runLogger.stageStart("dsl_extract");
|
|
10211
10320
|
try {
|
|
10212
10321
|
const isFrontend = isFrontendDeps(context.dependencies);
|
|
10213
|
-
if (isFrontend) console.log(
|
|
10322
|
+
if (isFrontend) console.log(import_chalk19.default.gray(" Frontend project detected \u2014 using ComponentSpec extractor"));
|
|
10214
10323
|
const dslExtractor = new DslExtractor(specProvider);
|
|
10215
10324
|
extractedDsl = await dslExtractor.extract(finalSpec, { auto: opts.auto, isFrontend });
|
|
10216
10325
|
if (extractedDsl) {
|
|
10217
10326
|
runLogger.stageEnd("dsl_extract", { endpoints: extractedDsl.endpoints?.length ?? 0, models: extractedDsl.models?.length ?? 0 });
|
|
10218
|
-
console.log(
|
|
10327
|
+
console.log(import_chalk19.default.green(" \u2714 DSL extracted and validated."));
|
|
10219
10328
|
} else {
|
|
10220
10329
|
runLogger.stageEnd("dsl_extract", { skipped: true });
|
|
10221
|
-
console.log(
|
|
10330
|
+
console.log(import_chalk19.default.yellow(" \u26A0 DSL skipped \u2014 codegen will use Spec + Tasks only."));
|
|
10222
10331
|
}
|
|
10223
10332
|
} catch (err) {
|
|
10224
10333
|
runLogger.stageFail("dsl_extract", err.message);
|
|
10225
|
-
console.log(
|
|
10334
|
+
console.log(import_chalk19.default.yellow(` \u26A0 DSL extraction error: ${err.message} \u2014 continuing without DSL.`));
|
|
10226
10335
|
}
|
|
10227
10336
|
}
|
|
10228
10337
|
const isFrontendProject2 = isFrontendDeps(context.dependencies ?? []);
|
|
10229
10338
|
const skipWorktree = opts.worktree ? false : opts.skipWorktree || isFrontendProject2;
|
|
10230
10339
|
let workingDir = currentDir;
|
|
10231
10340
|
if (!skipWorktree) {
|
|
10232
|
-
console.log(
|
|
10341
|
+
console.log(import_chalk19.default.blue("\n[4/6] Setting up git worktree..."));
|
|
10233
10342
|
const worktreeManager = new GitWorktreeManager(currentDir);
|
|
10234
10343
|
const worktreePath = await worktreeManager.createWorktree(idea);
|
|
10235
10344
|
if (worktreePath) workingDir = worktreePath;
|
|
10236
10345
|
} else {
|
|
10237
10346
|
const reason = opts.worktree ? "" : isFrontendProject2 ? " (frontend project \u2014 use --worktree to override)" : " (--skip-worktree)";
|
|
10238
|
-
console.log(
|
|
10347
|
+
console.log(import_chalk19.default.gray(`[4/6] Skipping worktree${reason}.`));
|
|
10239
10348
|
}
|
|
10240
10349
|
const specsDir = path22.join(workingDir, "specs");
|
|
10241
10350
|
await fs23.ensureDir(specsDir);
|
|
10242
10351
|
const { filePath: specFile, version: specVersion } = await nextVersionPath(specsDir, featureSlug);
|
|
10243
10352
|
await fs23.writeFile(specFile, finalSpec, "utf-8");
|
|
10244
|
-
console.log(
|
|
10245
|
-
[5/6] \u2714 Spec saved: ${specFile}`) +
|
|
10353
|
+
console.log(import_chalk19.default.green(`
|
|
10354
|
+
[5/6] \u2714 Spec saved: ${specFile}`) + import_chalk19.default.gray(` (v${specVersion})`));
|
|
10246
10355
|
let savedDslFile = null;
|
|
10247
10356
|
if (extractedDsl) {
|
|
10248
10357
|
const dslExtractor = new DslExtractor(specProvider);
|
|
10249
10358
|
savedDslFile = await dslExtractor.saveDsl(extractedDsl, specFile);
|
|
10250
|
-
console.log(
|
|
10359
|
+
console.log(import_chalk19.default.green(` \u2714 DSL saved : ${savedDslFile}`));
|
|
10251
10360
|
}
|
|
10252
10361
|
if (!opts.skipTasks) {
|
|
10253
10362
|
const taskGen = new TaskGenerator(specProvider);
|
|
10254
10363
|
let tasksToSave = initialTasks;
|
|
10255
10364
|
if (tasksToSave.length === 0) {
|
|
10256
|
-
console.log(
|
|
10365
|
+
console.log(import_chalk19.default.blue(`
|
|
10257
10366
|
Generating tasks (separate call)...`));
|
|
10258
10367
|
try {
|
|
10259
10368
|
tasksToSave = await taskGen.generateTasks(finalSpec, context);
|
|
10260
10369
|
} catch (err) {
|
|
10261
|
-
console.log(
|
|
10370
|
+
console.log(import_chalk19.default.yellow(` \u26A0 Task generation failed: ${err.message}`));
|
|
10262
10371
|
}
|
|
10263
10372
|
}
|
|
10264
10373
|
if (tasksToSave.length > 0) {
|
|
10265
10374
|
const sorted = taskGen.sortByLayer(tasksToSave);
|
|
10266
10375
|
const tasksFile = await taskGen.saveTasks(sorted, specFile);
|
|
10267
10376
|
printTasks(sorted);
|
|
10268
|
-
console.log(
|
|
10377
|
+
console.log(import_chalk19.default.green(` \u2714 Tasks saved: ${tasksFile}`));
|
|
10269
10378
|
} else {
|
|
10270
|
-
console.log(
|
|
10379
|
+
console.log(import_chalk19.default.yellow(" \u26A0 No tasks generated \u2014 code generation will use fallback file planning."));
|
|
10271
10380
|
}
|
|
10272
10381
|
}
|
|
10273
|
-
console.log(
|
|
10382
|
+
console.log(import_chalk19.default.blue(`
|
|
10274
10383
|
[6/6] Code generation (mode: ${codegenMode})...`));
|
|
10275
10384
|
const codegenProvider = codegenProviderName === specProviderName && codegenApiKey === specApiKey ? specProvider : createProvider(codegenProviderName, codegenApiKey, codegenModelName);
|
|
10276
10385
|
let generatedTestFiles = [];
|
|
10277
10386
|
if (opts.tdd && extractedDsl) {
|
|
10278
|
-
console.log(
|
|
10387
|
+
console.log(import_chalk19.default.cyan("\n[TDD] Generating pre-implementation tests (will fail until code is written)..."));
|
|
10279
10388
|
const testGen = new TestGenerator(codegenProvider);
|
|
10280
10389
|
generatedTestFiles = await testGen.generateTdd(extractedDsl, workingDir);
|
|
10281
10390
|
}
|
|
@@ -10289,27 +10398,29 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10289
10398
|
});
|
|
10290
10399
|
runLogger.stageEnd("codegen", { filesGenerated: generatedFiles.length });
|
|
10291
10400
|
if (opts.tdd) {
|
|
10292
|
-
console.log(
|
|
10401
|
+
console.log(import_chalk19.default.gray("\n[7/9] TDD mode \u2014 test files already written pre-implementation."));
|
|
10293
10402
|
} else if (opts.skipTests) {
|
|
10294
|
-
console.log(
|
|
10403
|
+
console.log(import_chalk19.default.gray("\n[7/9] Skipping test generation (--skip-tests)."));
|
|
10295
10404
|
} else if (!extractedDsl) {
|
|
10296
|
-
console.log(
|
|
10405
|
+
console.log(import_chalk19.default.gray("\n[7/9] Skipping test generation (no DSL available)."));
|
|
10297
10406
|
} else {
|
|
10298
|
-
console.log(
|
|
10407
|
+
console.log(import_chalk19.default.blue(`
|
|
10299
10408
|
[7/9] Test skeleton generation...`));
|
|
10300
10409
|
runLogger.stageStart("test_gen");
|
|
10301
10410
|
const testGen = new TestGenerator(codegenProvider);
|
|
10302
10411
|
generatedTestFiles = await testGen.generate(extractedDsl, workingDir);
|
|
10303
10412
|
runLogger.stageEnd("test_gen", { filesGenerated: generatedTestFiles.length });
|
|
10304
10413
|
}
|
|
10414
|
+
let compilePassed = false;
|
|
10305
10415
|
if (opts.skipErrorFeedback) {
|
|
10306
|
-
console.log(
|
|
10416
|
+
console.log(import_chalk19.default.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
|
|
10417
|
+
compilePassed = true;
|
|
10307
10418
|
} else {
|
|
10308
10419
|
if (opts.tdd) {
|
|
10309
|
-
console.log(
|
|
10420
|
+
console.log(import_chalk19.default.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
|
|
10310
10421
|
}
|
|
10311
10422
|
runLogger.stageStart("error_feedback");
|
|
10312
|
-
await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
|
|
10423
|
+
compilePassed = await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
|
|
10313
10424
|
maxCycles: opts.tdd ? 3 : 2
|
|
10314
10425
|
// TDD gets one extra cycle
|
|
10315
10426
|
});
|
|
@@ -10317,7 +10428,7 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10317
10428
|
}
|
|
10318
10429
|
let reviewResult = "";
|
|
10319
10430
|
if (!opts.skipReview) {
|
|
10320
|
-
console.log(
|
|
10431
|
+
console.log(import_chalk19.default.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
|
|
10321
10432
|
runLogger.stageStart("review");
|
|
10322
10433
|
const reviewer = new CodeReviewer(specProvider, currentDir);
|
|
10323
10434
|
const savedSpec = await fs23.readFile(specFile, "utf-8");
|
|
@@ -10335,20 +10446,30 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10335
10446
|
runLogger.stageEnd("review");
|
|
10336
10447
|
await accumulateReviewKnowledge(specProvider, currentDir, reviewResult);
|
|
10337
10448
|
}
|
|
10449
|
+
runLogger.stageStart("self_eval");
|
|
10450
|
+
const selfEvalResult = runSelfEval({
|
|
10451
|
+
dsl: extractedDsl,
|
|
10452
|
+
generatedFiles,
|
|
10453
|
+
compilePassed,
|
|
10454
|
+
reviewText: reviewResult,
|
|
10455
|
+
promptHash,
|
|
10456
|
+
logger: runLogger
|
|
10457
|
+
});
|
|
10458
|
+
printSelfEval(selfEvalResult);
|
|
10338
10459
|
runLogger.finish();
|
|
10339
|
-
console.log(
|
|
10340
|
-
console.log(
|
|
10341
|
-
if (savedDslFile) console.log(
|
|
10460
|
+
console.log(import_chalk19.default.bold.green("\n\u2714 All done!"));
|
|
10461
|
+
console.log(import_chalk19.default.gray(` Spec : ${specFile}`));
|
|
10462
|
+
if (savedDslFile) console.log(import_chalk19.default.gray(` DSL : ${savedDslFile}`));
|
|
10342
10463
|
if (generatedTestFiles.length > 0) {
|
|
10343
|
-
console.log(
|
|
10464
|
+
console.log(import_chalk19.default.gray(` Tests : ${generatedTestFiles.length} skeleton file(s) generated`));
|
|
10344
10465
|
}
|
|
10345
|
-
console.log(
|
|
10466
|
+
console.log(import_chalk19.default.gray(` Working dir : ${workingDir}`));
|
|
10346
10467
|
if (workingDir !== currentDir) {
|
|
10347
|
-
console.log(
|
|
10468
|
+
console.log(import_chalk19.default.gray(` Run \`cd ${workingDir}\` to enter the worktree.`));
|
|
10348
10469
|
}
|
|
10349
10470
|
runLogger.printSummary();
|
|
10350
10471
|
if (runSnapshot.fileCount > 0) {
|
|
10351
|
-
console.log(
|
|
10472
|
+
console.log(import_chalk19.default.gray(` To undo changes: ai-spec restore ${runId}`));
|
|
10352
10473
|
}
|
|
10353
10474
|
});
|
|
10354
10475
|
program.command("review").description("Run AI code review on current git diff against a spec").argument("[specFile]", "Path to spec file (auto-detects latest in specs/ if omitted)").option(
|
|
@@ -10368,7 +10489,7 @@ program.command("review").description("Run AI code review on current git diff ag
|
|
|
10368
10489
|
if (specFile && await fs23.pathExists(specFile)) {
|
|
10369
10490
|
specContent = await fs23.readFile(specFile, "utf-8");
|
|
10370
10491
|
resolvedSpecFile = specFile;
|
|
10371
|
-
console.log(
|
|
10492
|
+
console.log(import_chalk19.default.gray(`Using spec: ${specFile}`));
|
|
10372
10493
|
} else {
|
|
10373
10494
|
const specsDir = path22.join(currentDir, "specs");
|
|
10374
10495
|
if (await fs23.pathExists(specsDir)) {
|
|
@@ -10377,12 +10498,12 @@ program.command("review").description("Run AI code review on current git diff ag
|
|
|
10377
10498
|
const latest = path22.join(specsDir, files[0]);
|
|
10378
10499
|
specContent = await fs23.readFile(latest, "utf-8");
|
|
10379
10500
|
resolvedSpecFile = latest;
|
|
10380
|
-
console.log(
|
|
10501
|
+
console.log(import_chalk19.default.gray(`Auto-detected spec: specs/${files[0]}`));
|
|
10381
10502
|
}
|
|
10382
10503
|
}
|
|
10383
10504
|
}
|
|
10384
10505
|
if (!specContent) {
|
|
10385
|
-
console.log(
|
|
10506
|
+
console.log(import_chalk19.default.yellow("No spec file found. Running review without spec context."));
|
|
10386
10507
|
}
|
|
10387
10508
|
await reviewer.reviewCode(specContent, resolvedSpecFile);
|
|
10388
10509
|
await reviewer.printScoreTrend();
|
|
@@ -10409,15 +10530,15 @@ program.command("init").description(`Analyze codebase and generate Project Const
|
|
|
10409
10530
|
auto: opts.auto
|
|
10410
10531
|
});
|
|
10411
10532
|
if (result.written) {
|
|
10412
|
-
console.log(
|
|
10413
|
-
console.log(
|
|
10414
|
-
console.log(
|
|
10533
|
+
console.log(import_chalk19.default.blue("\n Summary:"));
|
|
10534
|
+
console.log(import_chalk19.default.gray(` Lines : ${result.before.totalLines} \u2192 ${result.after.totalLines} (${result.before.totalLines - result.after.totalLines > 0 ? "-" : "+"}${Math.abs(result.before.totalLines - result.after.totalLines)})`));
|
|
10535
|
+
console.log(import_chalk19.default.gray(` \xA79 : ${result.before.lessonCount} \u2192 ${result.after.lessonCount} lessons remaining`));
|
|
10415
10536
|
if (result.backupPath) {
|
|
10416
|
-
console.log(
|
|
10537
|
+
console.log(import_chalk19.default.gray(` Backup: ${path22.basename(result.backupPath)}`));
|
|
10417
10538
|
}
|
|
10418
10539
|
}
|
|
10419
10540
|
} catch (err) {
|
|
10420
|
-
console.error(
|
|
10541
|
+
console.error(import_chalk19.default.red(` \u2718 Consolidation failed: ${err.message}`));
|
|
10421
10542
|
process.exit(1);
|
|
10422
10543
|
}
|
|
10423
10544
|
return;
|
|
@@ -10425,14 +10546,14 @@ program.command("init").description(`Analyze codebase and generate Project Const
|
|
|
10425
10546
|
if (opts.global) {
|
|
10426
10547
|
const existing = await loadGlobalConstitution([currentDir]);
|
|
10427
10548
|
if (existing && !opts.force) {
|
|
10428
|
-
console.log(
|
|
10549
|
+
console.log(import_chalk19.default.yellow(`
|
|
10429
10550
|
Global constitution already exists at: ${existing.source}`));
|
|
10430
|
-
console.log(
|
|
10551
|
+
console.log(import_chalk19.default.gray(" Use --force to overwrite it."));
|
|
10431
10552
|
return;
|
|
10432
10553
|
}
|
|
10433
|
-
console.log(
|
|
10434
|
-
console.log(
|
|
10435
|
-
console.log(
|
|
10554
|
+
console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Generating Global Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10555
|
+
console.log(import_chalk19.default.gray(` Provider: ${providerName}/${modelName}`));
|
|
10556
|
+
console.log(import_chalk19.default.gray(" Scanning repos in workspace..."));
|
|
10436
10557
|
const loader = new ContextLoader(currentDir);
|
|
10437
10558
|
const ctx = await loader.loadProjectContext();
|
|
10438
10559
|
const summary = [
|
|
@@ -10444,56 +10565,56 @@ program.command("init").description(`Analyze codebase and generate Project Const
|
|
|
10444
10565
|
try {
|
|
10445
10566
|
globalConstitution = await provider.generate(prompt, globalConstitutionSystemPrompt);
|
|
10446
10567
|
} catch (err) {
|
|
10447
|
-
console.error(
|
|
10568
|
+
console.error(import_chalk19.default.red(" \u2718 Failed to generate global constitution:"), err);
|
|
10448
10569
|
process.exit(1);
|
|
10449
10570
|
}
|
|
10450
10571
|
const saved2 = await saveGlobalConstitution(globalConstitution, currentDir);
|
|
10451
|
-
console.log(
|
|
10572
|
+
console.log(import_chalk19.default.green(`
|
|
10452
10573
|
\u2714 Global constitution saved: ${saved2}`));
|
|
10453
|
-
console.log(
|
|
10454
|
-
console.log(
|
|
10455
|
-
console.log(
|
|
10456
|
-
console.log(
|
|
10574
|
+
console.log(import_chalk19.default.gray(" This will be automatically merged into all project constitutions in this workspace."));
|
|
10575
|
+
console.log(import_chalk19.default.gray(" Project-level rules always override global rules.\n"));
|
|
10576
|
+
console.log(import_chalk19.default.bold(" Preview:"));
|
|
10577
|
+
console.log(import_chalk19.default.gray(globalConstitution.split("\n").slice(0, 12).join("\n")));
|
|
10457
10578
|
if (globalConstitution.split("\n").length > 12) {
|
|
10458
|
-
console.log(
|
|
10579
|
+
console.log(import_chalk19.default.gray(` ... (${globalConstitution.split("\n").length} lines total)`));
|
|
10459
10580
|
}
|
|
10460
10581
|
return;
|
|
10461
10582
|
}
|
|
10462
10583
|
const constitutionPath = path22.join(currentDir, CONSTITUTION_FILE);
|
|
10463
10584
|
if (!opts.force && await fs23.pathExists(constitutionPath)) {
|
|
10464
|
-
console.log(
|
|
10585
|
+
console.log(import_chalk19.default.yellow(`
|
|
10465
10586
|
${CONSTITUTION_FILE} already exists.`));
|
|
10466
|
-
console.log(
|
|
10467
|
-
console.log(
|
|
10587
|
+
console.log(import_chalk19.default.gray(" Use --force to overwrite it."));
|
|
10588
|
+
console.log(import_chalk19.default.gray(` Or edit it directly: ${constitutionPath}`));
|
|
10468
10589
|
return;
|
|
10469
10590
|
}
|
|
10470
|
-
console.log(
|
|
10471
|
-
console.log(
|
|
10472
|
-
console.log(
|
|
10591
|
+
console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Generating Project Constitution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10592
|
+
console.log(import_chalk19.default.gray(` Provider: ${providerName}/${modelName}`));
|
|
10593
|
+
console.log(import_chalk19.default.gray(" Analyzing codebase..."));
|
|
10473
10594
|
const generator = new ConstitutionGenerator(provider);
|
|
10474
10595
|
let constitution;
|
|
10475
10596
|
try {
|
|
10476
10597
|
constitution = await generator.generate(currentDir);
|
|
10477
10598
|
} catch (err) {
|
|
10478
|
-
console.error(
|
|
10599
|
+
console.error(import_chalk19.default.red(" \u2718 Failed to generate constitution:"), err);
|
|
10479
10600
|
process.exit(1);
|
|
10480
10601
|
}
|
|
10481
10602
|
const saved = await generator.saveConstitution(currentDir, constitution);
|
|
10482
10603
|
const globalResult = await loadGlobalConstitution([path22.dirname(currentDir)]);
|
|
10483
10604
|
if (globalResult) {
|
|
10484
|
-
console.log(
|
|
10605
|
+
console.log(import_chalk19.default.cyan(`
|
|
10485
10606
|
\u2139 Global constitution detected: ${globalResult.source}`));
|
|
10486
|
-
console.log(
|
|
10487
|
-
console.log(
|
|
10607
|
+
console.log(import_chalk19.default.gray(" It will be merged with this project constitution at runtime."));
|
|
10608
|
+
console.log(import_chalk19.default.gray(" Project rules take priority over global rules."));
|
|
10488
10609
|
}
|
|
10489
|
-
console.log(
|
|
10610
|
+
console.log(import_chalk19.default.green(`
|
|
10490
10611
|
\u2714 Constitution saved: ${saved}`));
|
|
10491
|
-
console.log(
|
|
10492
|
-
console.log(
|
|
10493
|
-
console.log(
|
|
10494
|
-
console.log(
|
|
10612
|
+
console.log(import_chalk19.default.gray(" This file will be automatically used in all future `ai-spec create` runs."));
|
|
10613
|
+
console.log(import_chalk19.default.gray(" Edit it to add custom rules or red lines for your project.\n"));
|
|
10614
|
+
console.log(import_chalk19.default.bold(" Preview:"));
|
|
10615
|
+
console.log(import_chalk19.default.gray(constitution.split("\n").slice(0, 15).join("\n")));
|
|
10495
10616
|
if (constitution.split("\n").length > 15) {
|
|
10496
|
-
console.log(
|
|
10617
|
+
console.log(import_chalk19.default.gray(` ... (${constitution.split("\n").length} lines total)`));
|
|
10497
10618
|
}
|
|
10498
10619
|
});
|
|
10499
10620
|
program.command("config").description(`Set default configuration for this project (saved to ${CONFIG_FILE})`).option("--provider <name>", "Default AI provider for spec generation").option("--model <name>", "Default model for spec generation").option(
|
|
@@ -10504,41 +10625,41 @@ program.command("config").description(`Set default configuration for this projec
|
|
|
10504
10625
|
const configPath = path22.join(currentDir, CONFIG_FILE);
|
|
10505
10626
|
if (opts.clearKeys) {
|
|
10506
10627
|
await clearAllKeys();
|
|
10507
|
-
console.log(
|
|
10628
|
+
console.log(import_chalk19.default.green(`\u2714 All saved API keys cleared.`));
|
|
10508
10629
|
return;
|
|
10509
10630
|
}
|
|
10510
10631
|
if (opts.clearKey) {
|
|
10511
10632
|
await clearKey(opts.clearKey);
|
|
10512
|
-
console.log(
|
|
10633
|
+
console.log(import_chalk19.default.green(`\u2714 Saved key for "${opts.clearKey}" removed.`));
|
|
10513
10634
|
return;
|
|
10514
10635
|
}
|
|
10515
10636
|
if (opts.listKeys) {
|
|
10516
10637
|
const store = await fs23.readJson(KEY_STORE_FILE).catch(() => ({}));
|
|
10517
10638
|
const providers = Object.keys(store);
|
|
10518
10639
|
if (providers.length === 0) {
|
|
10519
|
-
console.log(
|
|
10640
|
+
console.log(import_chalk19.default.gray("No saved API keys."));
|
|
10520
10641
|
} else {
|
|
10521
|
-
console.log(
|
|
10642
|
+
console.log(import_chalk19.default.bold("Saved API keys:"));
|
|
10522
10643
|
for (const p of providers) {
|
|
10523
10644
|
const k2 = store[p];
|
|
10524
|
-
console.log(
|
|
10645
|
+
console.log(import_chalk19.default.gray(` ${p}: ${k2.slice(0, 6)}...${k2.slice(-4)}`));
|
|
10525
10646
|
}
|
|
10526
|
-
console.log(
|
|
10647
|
+
console.log(import_chalk19.default.gray(`
|
|
10527
10648
|
File: ${KEY_STORE_FILE}`));
|
|
10528
10649
|
}
|
|
10529
10650
|
return;
|
|
10530
10651
|
}
|
|
10531
10652
|
if (opts.reset) {
|
|
10532
10653
|
await fs23.writeJson(configPath, {}, { spaces: 2 });
|
|
10533
|
-
console.log(
|
|
10654
|
+
console.log(import_chalk19.default.green(`\u2714 Config reset: ${configPath}`));
|
|
10534
10655
|
return;
|
|
10535
10656
|
}
|
|
10536
10657
|
const existing = await loadConfig(currentDir);
|
|
10537
10658
|
if (opts.show) {
|
|
10538
10659
|
if (Object.keys(existing).length === 0) {
|
|
10539
|
-
console.log(
|
|
10660
|
+
console.log(import_chalk19.default.gray("No config file found. Using built-in defaults."));
|
|
10540
10661
|
} else {
|
|
10541
|
-
console.log(
|
|
10662
|
+
console.log(import_chalk19.default.bold(`${configPath}:`));
|
|
10542
10663
|
console.log(JSON.stringify(existing, null, 2));
|
|
10543
10664
|
}
|
|
10544
10665
|
return;
|
|
@@ -10552,27 +10673,27 @@ File: ${KEY_STORE_FILE}`));
|
|
|
10552
10673
|
if (opts.minSpecScore !== void 0) {
|
|
10553
10674
|
const score = parseInt(opts.minSpecScore, 10);
|
|
10554
10675
|
if (isNaN(score) || score < 0 || score > 10) {
|
|
10555
|
-
console.error(
|
|
10676
|
+
console.error(import_chalk19.default.red(" --min-spec-score must be a number between 0 and 10"));
|
|
10556
10677
|
process.exit(1);
|
|
10557
10678
|
}
|
|
10558
10679
|
updated.minSpecScore = score;
|
|
10559
10680
|
}
|
|
10560
10681
|
await fs23.writeJson(configPath, updated, { spaces: 2 });
|
|
10561
|
-
console.log(
|
|
10682
|
+
console.log(import_chalk19.default.green(`\u2714 Config saved to ${configPath}`));
|
|
10562
10683
|
console.log(JSON.stringify(updated, null, 2));
|
|
10563
10684
|
});
|
|
10564
10685
|
program.command("model").description("Interactively switch the active AI provider/model and save to .ai-spec.json").option("--list", "List all available providers and models").action(async (opts) => {
|
|
10565
10686
|
const currentDir = process.cwd();
|
|
10566
10687
|
const configPath = path22.join(currentDir, CONFIG_FILE);
|
|
10567
10688
|
if (opts.list) {
|
|
10568
|
-
console.log(
|
|
10689
|
+
console.log(import_chalk19.default.bold("\nAvailable providers & models:\n"));
|
|
10569
10690
|
for (const [key, meta] of Object.entries(PROVIDER_CATALOG)) {
|
|
10570
10691
|
console.log(
|
|
10571
|
-
` ${
|
|
10692
|
+
` ${import_chalk19.default.bold.cyan(key.padEnd(10))} ${import_chalk19.default.white(meta.displayName)}`
|
|
10572
10693
|
);
|
|
10573
|
-
console.log(
|
|
10694
|
+
console.log(import_chalk19.default.gray(` ${meta.description}`));
|
|
10574
10695
|
console.log(
|
|
10575
|
-
|
|
10696
|
+
import_chalk19.default.gray(
|
|
10576
10697
|
` env: ${meta.envKey} | models: ${meta.models.join(", ")}`
|
|
10577
10698
|
)
|
|
10578
10699
|
);
|
|
@@ -10581,10 +10702,10 @@ program.command("model").description("Interactively switch the active AI provide
|
|
|
10581
10702
|
return;
|
|
10582
10703
|
}
|
|
10583
10704
|
const existing = await loadConfig(currentDir);
|
|
10584
|
-
console.log(
|
|
10705
|
+
console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Model Switcher \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"));
|
|
10585
10706
|
if (Object.keys(existing).length > 0) {
|
|
10586
10707
|
console.log(
|
|
10587
|
-
|
|
10708
|
+
import_chalk19.default.gray(
|
|
10588
10709
|
` Current: spec=${existing.provider ?? "gemini"}/${existing.model ?? DEFAULT_MODELS[existing.provider ?? "gemini"]}` + (existing.codegenProvider ? ` codegen=${existing.codegenProvider}/${existing.codegenModel ?? ""}` : "")
|
|
10589
10710
|
)
|
|
10590
10711
|
);
|
|
@@ -10602,7 +10723,7 @@ program.command("model").description("Interactively switch the active AI provide
|
|
|
10602
10723
|
const providerKey = await (0, import_prompts3.select)({
|
|
10603
10724
|
message: `${label} \u2014 select provider:`,
|
|
10604
10725
|
choices: Object.entries(PROVIDER_CATALOG).map(([key, meta2]) => ({
|
|
10605
|
-
name: `${meta2.displayName.padEnd(22)} ${
|
|
10726
|
+
name: `${meta2.displayName.padEnd(22)} ${import_chalk19.default.gray(meta2.description)}`,
|
|
10606
10727
|
value: key,
|
|
10607
10728
|
short: meta2.displayName
|
|
10608
10729
|
}))
|
|
@@ -10610,7 +10731,7 @@ program.command("model").description("Interactively switch the active AI provide
|
|
|
10610
10731
|
const meta = PROVIDER_CATALOG[providerKey];
|
|
10611
10732
|
const modelChoices = [
|
|
10612
10733
|
...meta.models.map((m) => ({ name: m, value: m })),
|
|
10613
|
-
{ name:
|
|
10734
|
+
{ name: import_chalk19.default.italic("\u270E Enter custom model name..."), value: "__custom__" }
|
|
10614
10735
|
];
|
|
10615
10736
|
let chosenModel = await (0, import_prompts3.select)({
|
|
10616
10737
|
message: `${label} \u2014 select model (${meta.displayName}):`,
|
|
@@ -10644,37 +10765,37 @@ program.command("model").description("Interactively switch the active AI provide
|
|
|
10644
10765
|
if (!updated.codegen || updated.codegen === "claude-code") {
|
|
10645
10766
|
updated.codegen = "api";
|
|
10646
10767
|
console.log(
|
|
10647
|
-
|
|
10768
|
+
import_chalk19.default.yellow(
|
|
10648
10769
|
`
|
|
10649
10770
|
\u26A0 provider "${effectiveCodegenProvider}" \u4E0D\u652F\u6301 "claude-code" \u6A21\u5F0F\u3002`
|
|
10650
10771
|
)
|
|
10651
10772
|
);
|
|
10652
|
-
console.log(
|
|
10773
|
+
console.log(import_chalk19.default.gray(` \u5DF2\u81EA\u52A8\u5C06 codegen \u6A21\u5F0F\u8BBE\u4E3A "api"\u3002`));
|
|
10653
10774
|
}
|
|
10654
10775
|
}
|
|
10655
10776
|
}
|
|
10656
|
-
console.log(
|
|
10657
|
-
console.log(
|
|
10777
|
+
console.log(import_chalk19.default.blue("\n Preview:"));
|
|
10778
|
+
console.log(import_chalk19.default.gray(` spec \u2192 ${updated.provider}/${updated.model}`));
|
|
10658
10779
|
if (updated.codegenProvider) {
|
|
10659
10780
|
console.log(
|
|
10660
|
-
|
|
10781
|
+
import_chalk19.default.gray(
|
|
10661
10782
|
` codegen \u2192 ${updated.codegenProvider}/${updated.codegenModel} (mode: ${updated.codegen ?? "claude-code"})`
|
|
10662
10783
|
)
|
|
10663
10784
|
);
|
|
10664
10785
|
}
|
|
10665
10786
|
const ok = await (0, import_prompts3.confirm)({ message: "Save to .ai-spec.json?", default: true });
|
|
10666
10787
|
if (!ok) {
|
|
10667
|
-
console.log(
|
|
10788
|
+
console.log(import_chalk19.default.gray(" Cancelled."));
|
|
10668
10789
|
return;
|
|
10669
10790
|
}
|
|
10670
10791
|
await fs23.writeJson(configPath, updated, { spaces: 2 });
|
|
10671
|
-
console.log(
|
|
10792
|
+
console.log(import_chalk19.default.green(`
|
|
10672
10793
|
\u2714 Saved to ${configPath}`));
|
|
10673
10794
|
const providerToCheck = updated.provider ?? "gemini";
|
|
10674
10795
|
const envKey = ENV_KEY_MAP[providerToCheck];
|
|
10675
10796
|
if (envKey && !process.env[envKey]) {
|
|
10676
10797
|
console.log(
|
|
10677
|
-
|
|
10798
|
+
import_chalk19.default.yellow(
|
|
10678
10799
|
` \u26A0 Remember to set ${envKey} in your environment or .env file.`
|
|
10679
10800
|
)
|
|
10680
10801
|
);
|
|
@@ -10693,29 +10814,29 @@ async function runSingleRepoPipelineInWorkspace(opts) {
|
|
|
10693
10814
|
cliOpts,
|
|
10694
10815
|
contractContextSection
|
|
10695
10816
|
} = opts;
|
|
10696
|
-
console.log(
|
|
10817
|
+
console.log(import_chalk19.default.blue(`
|
|
10697
10818
|
[${repoName}] Loading project context...`));
|
|
10698
10819
|
const loader = new ContextLoader(repoAbsPath);
|
|
10699
10820
|
let context = await loader.loadProjectContext();
|
|
10700
10821
|
const { type: detectedRepoType } = await detectRepoType(repoAbsPath);
|
|
10701
|
-
console.log(
|
|
10702
|
-
console.log(
|
|
10822
|
+
console.log(import_chalk19.default.gray(` Tech stack: ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
|
|
10823
|
+
console.log(import_chalk19.default.gray(` Dependencies: ${context.dependencies.length} packages`));
|
|
10703
10824
|
if (context.constitution && context.constitution.length > 6e3) {
|
|
10704
|
-
console.log(
|
|
10825
|
+
console.log(import_chalk19.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
|
|
10705
10826
|
}
|
|
10706
10827
|
if (!context.constitution) {
|
|
10707
|
-
console.log(
|
|
10828
|
+
console.log(import_chalk19.default.yellow(` Constitution: not found \u2014 auto-generating...`));
|
|
10708
10829
|
try {
|
|
10709
10830
|
const constitutionGen = new ConstitutionGenerator(specProvider);
|
|
10710
10831
|
const constitutionContent = await constitutionGen.generate(repoAbsPath);
|
|
10711
10832
|
await constitutionGen.saveConstitution(repoAbsPath, constitutionContent);
|
|
10712
10833
|
context.constitution = constitutionContent;
|
|
10713
|
-
console.log(
|
|
10834
|
+
console.log(import_chalk19.default.green(` Constitution: generated`));
|
|
10714
10835
|
} catch (err) {
|
|
10715
|
-
console.log(
|
|
10836
|
+
console.log(import_chalk19.default.yellow(` Constitution: auto-generation failed (${err.message}), continuing.`));
|
|
10716
10837
|
}
|
|
10717
10838
|
} else {
|
|
10718
|
-
console.log(
|
|
10839
|
+
console.log(import_chalk19.default.green(` Constitution: found`));
|
|
10719
10840
|
}
|
|
10720
10841
|
let fullIdea = idea;
|
|
10721
10842
|
if (contractContextSection) {
|
|
@@ -10723,58 +10844,58 @@ async function runSingleRepoPipelineInWorkspace(opts) {
|
|
|
10723
10844
|
|
|
10724
10845
|
${contractContextSection}`;
|
|
10725
10846
|
}
|
|
10726
|
-
console.log(
|
|
10847
|
+
console.log(import_chalk19.default.blue(` [${repoName}] Generating spec...`));
|
|
10727
10848
|
let finalSpec;
|
|
10728
10849
|
try {
|
|
10729
10850
|
const result = await generateSpecWithTasks(specProvider, fullIdea, context);
|
|
10730
10851
|
finalSpec = result.spec;
|
|
10731
|
-
console.log(
|
|
10852
|
+
console.log(import_chalk19.default.green(` Spec generated.`));
|
|
10732
10853
|
} catch (err) {
|
|
10733
|
-
console.error(
|
|
10854
|
+
console.error(import_chalk19.default.red(` Spec generation failed: ${err.message}`));
|
|
10734
10855
|
return { dsl: null, specFile: null };
|
|
10735
10856
|
}
|
|
10736
10857
|
let extractedDsl = null;
|
|
10737
10858
|
if (!cliOpts.skipDsl) {
|
|
10738
|
-
console.log(
|
|
10859
|
+
console.log(import_chalk19.default.blue(` [${repoName}] Extracting DSL...`));
|
|
10739
10860
|
try {
|
|
10740
10861
|
const dslExtractor = new DslExtractor(specProvider);
|
|
10741
10862
|
const repoIsFrontend = isFrontendDeps(context.dependencies);
|
|
10742
10863
|
extractedDsl = await dslExtractor.extract(finalSpec, { auto: true, isFrontend: repoIsFrontend });
|
|
10743
10864
|
if (extractedDsl) {
|
|
10744
|
-
console.log(
|
|
10865
|
+
console.log(import_chalk19.default.green(` DSL extracted.`));
|
|
10745
10866
|
}
|
|
10746
10867
|
} catch (err) {
|
|
10747
|
-
console.log(
|
|
10868
|
+
console.log(import_chalk19.default.yellow(` DSL extraction failed: ${err.message}`));
|
|
10748
10869
|
}
|
|
10749
10870
|
}
|
|
10750
10871
|
const isFrontendRepo = isFrontendDeps(context.dependencies ?? []);
|
|
10751
10872
|
const skipWorktreeForRepo = cliOpts.worktree ? false : cliOpts.skipWorktree || isFrontendRepo;
|
|
10752
10873
|
let workingDir = repoAbsPath;
|
|
10753
10874
|
if (!skipWorktreeForRepo) {
|
|
10754
|
-
console.log(
|
|
10875
|
+
console.log(import_chalk19.default.blue(` [${repoName}] Setting up git worktree...`));
|
|
10755
10876
|
try {
|
|
10756
10877
|
const worktreeManager = new GitWorktreeManager(repoAbsPath);
|
|
10757
10878
|
const worktreePath = await worktreeManager.createWorktree(idea);
|
|
10758
10879
|
if (worktreePath) workingDir = worktreePath;
|
|
10759
10880
|
} catch (err) {
|
|
10760
|
-
console.log(
|
|
10881
|
+
console.log(import_chalk19.default.yellow(` Worktree setup failed: ${err.message}. Using main branch.`));
|
|
10761
10882
|
}
|
|
10762
10883
|
} else {
|
|
10763
|
-
console.log(
|
|
10884
|
+
console.log(import_chalk19.default.gray(` [${repoName}] Skipping worktree${isFrontendRepo ? " (frontend repo)" : ""}.`));
|
|
10764
10885
|
}
|
|
10765
10886
|
const specsDir = path22.join(workingDir, "specs");
|
|
10766
10887
|
await fs23.ensureDir(specsDir);
|
|
10767
10888
|
const featureSlug = slugify(idea);
|
|
10768
10889
|
const { filePath: specFile } = await nextVersionPath(specsDir, featureSlug);
|
|
10769
10890
|
await fs23.writeFile(specFile, finalSpec, "utf-8");
|
|
10770
|
-
console.log(
|
|
10891
|
+
console.log(import_chalk19.default.green(` Spec saved: ${path22.relative(repoAbsPath, specFile)}`));
|
|
10771
10892
|
let savedDslFile = null;
|
|
10772
10893
|
if (extractedDsl) {
|
|
10773
10894
|
const dslExtractorForSave = new DslExtractor(specProvider);
|
|
10774
10895
|
savedDslFile = await dslExtractorForSave.saveDsl(extractedDsl, specFile);
|
|
10775
|
-
console.log(
|
|
10896
|
+
console.log(import_chalk19.default.green(` DSL saved: ${path22.relative(repoAbsPath, savedDslFile)}`));
|
|
10776
10897
|
}
|
|
10777
|
-
console.log(
|
|
10898
|
+
console.log(import_chalk19.default.blue(` [${repoName}] Running code generation (mode: ${codegenMode})...`));
|
|
10778
10899
|
try {
|
|
10779
10900
|
const codegen = new CodeGenerator(codegenProvider, codegenMode);
|
|
10780
10901
|
await codegen.generateCode(specFile, workingDir, context, {
|
|
@@ -10782,29 +10903,29 @@ ${contractContextSection}`;
|
|
|
10782
10903
|
dslFilePath: savedDslFile ?? void 0,
|
|
10783
10904
|
repoType: detectedRepoType
|
|
10784
10905
|
});
|
|
10785
|
-
console.log(
|
|
10906
|
+
console.log(import_chalk19.default.green(` Code generation complete.`));
|
|
10786
10907
|
} catch (err) {
|
|
10787
|
-
console.log(
|
|
10908
|
+
console.log(import_chalk19.default.yellow(` Code generation failed: ${err.message}`));
|
|
10788
10909
|
}
|
|
10789
10910
|
if (!cliOpts.skipTests && extractedDsl) {
|
|
10790
|
-
console.log(
|
|
10911
|
+
console.log(import_chalk19.default.blue(` [${repoName}] Generating test skeletons...`));
|
|
10791
10912
|
try {
|
|
10792
10913
|
const testGen = new TestGenerator(codegenProvider);
|
|
10793
10914
|
const testFiles = await testGen.generate(extractedDsl, workingDir);
|
|
10794
|
-
console.log(
|
|
10915
|
+
console.log(import_chalk19.default.green(` ${testFiles.length} test file(s) generated.`));
|
|
10795
10916
|
} catch (err) {
|
|
10796
|
-
console.log(
|
|
10917
|
+
console.log(import_chalk19.default.yellow(` Test generation failed: ${err.message}`));
|
|
10797
10918
|
}
|
|
10798
10919
|
}
|
|
10799
10920
|
if (!cliOpts.skipErrorFeedback) {
|
|
10800
10921
|
try {
|
|
10801
10922
|
await runErrorFeedback(codegenProvider, workingDir, extractedDsl, { maxCycles: 1 });
|
|
10802
10923
|
} catch (err) {
|
|
10803
|
-
console.log(
|
|
10924
|
+
console.log(import_chalk19.default.yellow(` Error feedback failed: ${err.message}`));
|
|
10804
10925
|
}
|
|
10805
10926
|
}
|
|
10806
10927
|
if (!cliOpts.skipReview) {
|
|
10807
|
-
console.log(
|
|
10928
|
+
console.log(import_chalk19.default.blue(` [${repoName}] Running code review...`));
|
|
10808
10929
|
try {
|
|
10809
10930
|
const reviewer = new CodeReviewer(specProvider);
|
|
10810
10931
|
const originalDir = process.cwd();
|
|
@@ -10816,9 +10937,9 @@ ${contractContextSection}`;
|
|
|
10816
10937
|
process.chdir(originalDir);
|
|
10817
10938
|
}
|
|
10818
10939
|
await accumulateReviewKnowledge(specProvider, repoAbsPath, reviewResult);
|
|
10819
|
-
console.log(
|
|
10940
|
+
console.log(import_chalk19.default.green(` Code review complete.`));
|
|
10820
10941
|
} catch (err) {
|
|
10821
|
-
console.log(
|
|
10942
|
+
console.log(import_chalk19.default.yellow(` Code review failed: ${err.message}`));
|
|
10822
10943
|
}
|
|
10823
10944
|
}
|
|
10824
10945
|
return { dsl: extractedDsl, specFile };
|
|
@@ -10841,7 +10962,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
|
|
|
10841
10962
|
codegenModel: codegenModelName
|
|
10842
10963
|
});
|
|
10843
10964
|
const workspaceLoader = new WorkspaceLoader(currentDir);
|
|
10844
|
-
console.log(
|
|
10965
|
+
console.log(import_chalk19.default.blue("\n[W1] Loading per-repo contexts..."));
|
|
10845
10966
|
const contexts = /* @__PURE__ */ new Map();
|
|
10846
10967
|
const frontendContexts = /* @__PURE__ */ new Map();
|
|
10847
10968
|
for (const repo of workspace.repos) {
|
|
@@ -10853,27 +10974,27 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
|
|
|
10853
10974
|
if (repo.role === "frontend" || repo.role === "mobile") {
|
|
10854
10975
|
const fctx = await loadFrontendContext(repoAbsPath);
|
|
10855
10976
|
frontendContexts.set(repo.name, fctx);
|
|
10856
|
-
console.log(
|
|
10977
|
+
console.log(import_chalk19.default.gray(` ${repo.name}: ${fctx.framework} / ${fctx.httpClient} / hooks:${fctx.hookFiles.length} stores:${fctx.storeFiles.length}`));
|
|
10857
10978
|
} else {
|
|
10858
|
-
console.log(
|
|
10979
|
+
console.log(import_chalk19.default.gray(` ${repo.name}: ${ctx.techStack.join(", ") || "unknown"} (${ctx.dependencies.length} deps)`));
|
|
10859
10980
|
}
|
|
10860
10981
|
} catch (err) {
|
|
10861
|
-
console.log(
|
|
10982
|
+
console.log(import_chalk19.default.yellow(` ${repo.name}: context load failed \u2014 ${err.message}`));
|
|
10862
10983
|
}
|
|
10863
10984
|
}
|
|
10864
|
-
console.log(
|
|
10985
|
+
console.log(import_chalk19.default.blue("\n[W2] Decomposing requirement across repos..."));
|
|
10865
10986
|
const decomposer = new RequirementDecomposer(specProvider);
|
|
10866
10987
|
let decomposition;
|
|
10867
10988
|
try {
|
|
10868
10989
|
decomposition = await decomposer.decompose(idea, workspace, contexts, frontendContexts);
|
|
10869
|
-
console.log(
|
|
10870
|
-
console.log(
|
|
10990
|
+
console.log(import_chalk19.default.green(` Summary: ${decomposition.summary}`));
|
|
10991
|
+
console.log(import_chalk19.default.gray(` Repos affected: ${decomposition.repos.map((r) => r.repoName).join(", ")}`));
|
|
10871
10992
|
if (decomposition.coordinationNotes) {
|
|
10872
|
-
console.log(
|
|
10993
|
+
console.log(import_chalk19.default.gray(` Coordination: ${decomposition.coordinationNotes}`));
|
|
10873
10994
|
}
|
|
10874
10995
|
} catch (err) {
|
|
10875
|
-
console.error(
|
|
10876
|
-
console.log(
|
|
10996
|
+
console.error(import_chalk19.default.red(` Decomposition failed: ${err.message}`));
|
|
10997
|
+
console.log(import_chalk19.default.yellow(" Falling back to running all repos independently."));
|
|
10877
10998
|
decomposition = {
|
|
10878
10999
|
originalRequirement: idea,
|
|
10879
11000
|
summary: idea,
|
|
@@ -10889,11 +11010,11 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
|
|
|
10889
11010
|
};
|
|
10890
11011
|
}
|
|
10891
11012
|
if (!opts.auto) {
|
|
10892
|
-
console.log(
|
|
10893
|
-
console.log(
|
|
11013
|
+
console.log(import_chalk19.default.cyan("\n[W3] Decomposition Preview:"));
|
|
11014
|
+
console.log(import_chalk19.default.cyan("\u2500".repeat(52)));
|
|
10894
11015
|
for (const r of decomposition.repos) {
|
|
10895
|
-
console.log(
|
|
10896
|
-
console.log(
|
|
11016
|
+
console.log(import_chalk19.default.bold(` ${r.repoName} (${r.role})`));
|
|
11017
|
+
console.log(import_chalk19.default.gray(` ${r.specIdea.slice(0, 150)}${r.specIdea.length > 150 ? "..." : ""}`));
|
|
10897
11018
|
if (r.uxDecisions) {
|
|
10898
11019
|
const ux = r.uxDecisions;
|
|
10899
11020
|
const uxSummary = [
|
|
@@ -10902,13 +11023,13 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
|
|
|
10902
11023
|
ux.optimisticUpdate ? "optimistic-update" : "",
|
|
10903
11024
|
ux.errorRollback ? "rollback" : ""
|
|
10904
11025
|
].filter(Boolean).join(", ");
|
|
10905
|
-
if (uxSummary) console.log(
|
|
11026
|
+
if (uxSummary) console.log(import_chalk19.default.cyan(` UX: ${uxSummary}`));
|
|
10906
11027
|
}
|
|
10907
11028
|
if (r.dependsOnRepos.length > 0) {
|
|
10908
|
-
console.log(
|
|
11029
|
+
console.log(import_chalk19.default.gray(` Depends on: ${r.dependsOnRepos.join(", ")}`));
|
|
10909
11030
|
}
|
|
10910
11031
|
}
|
|
10911
|
-
console.log(
|
|
11032
|
+
console.log(import_chalk19.default.cyan("\u2500".repeat(52)));
|
|
10912
11033
|
const gate = await (0, import_prompts3.select)({
|
|
10913
11034
|
message: "Proceed with multi-repo pipeline?",
|
|
10914
11035
|
choices: [
|
|
@@ -10917,24 +11038,24 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
|
|
|
10917
11038
|
]
|
|
10918
11039
|
});
|
|
10919
11040
|
if (gate === "abort") {
|
|
10920
|
-
console.log(
|
|
11041
|
+
console.log(import_chalk19.default.yellow(" Aborted."));
|
|
10921
11042
|
process.exit(0);
|
|
10922
11043
|
}
|
|
10923
11044
|
}
|
|
10924
11045
|
const sortedRepoRequirements = RequirementDecomposer.sortByDependency(decomposition.repos);
|
|
10925
11046
|
const contractDsls = /* @__PURE__ */ new Map();
|
|
10926
|
-
console.log(
|
|
11047
|
+
console.log(import_chalk19.default.blue(`
|
|
10927
11048
|
[W4] Running pipeline for ${sortedRepoRequirements.length} repo(s)...`));
|
|
10928
11049
|
const results = [];
|
|
10929
11050
|
for (const repoReq of sortedRepoRequirements) {
|
|
10930
11051
|
const repoConfig = workspace.repos.find((r) => r.name === repoReq.repoName);
|
|
10931
11052
|
if (!repoConfig) {
|
|
10932
|
-
console.log(
|
|
11053
|
+
console.log(import_chalk19.default.yellow(` Skipping ${repoReq.repoName} \u2014 not found in workspace config.`));
|
|
10933
11054
|
results.push({ repoName: repoReq.repoName, status: "skipped", specFile: null, dsl: null, repoAbsPath: "", role: repoReq.role });
|
|
10934
11055
|
continue;
|
|
10935
11056
|
}
|
|
10936
11057
|
const repoAbsPath = workspaceLoader.resolveAbsPath(repoConfig);
|
|
10937
|
-
console.log(
|
|
11058
|
+
console.log(import_chalk19.default.bold.blue(`
|
|
10938
11059
|
\u2500\u2500 ${repoReq.repoName} (${repoReq.role}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`));
|
|
10939
11060
|
let contractContextSection;
|
|
10940
11061
|
if (repoReq.dependsOnRepos.length > 0) {
|
|
@@ -10942,7 +11063,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
|
|
|
10942
11063
|
for (const depName of repoReq.dependsOnRepos) {
|
|
10943
11064
|
const depDsl = contractDsls.get(depName);
|
|
10944
11065
|
if (depDsl) {
|
|
10945
|
-
console.log(
|
|
11066
|
+
console.log(import_chalk19.default.gray(` Using API contract from: ${depName}`));
|
|
10946
11067
|
const contract = buildFrontendApiContract(depDsl);
|
|
10947
11068
|
contractParts.push(buildContractContextSection(contract));
|
|
10948
11069
|
}
|
|
@@ -10962,7 +11083,7 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
|
|
|
10962
11083
|
frontendContext: frontendCtx
|
|
10963
11084
|
});
|
|
10964
11085
|
contractContextSection = void 0;
|
|
10965
|
-
console.log(
|
|
11086
|
+
console.log(import_chalk19.default.gray(` Frontend context: ${frontendCtx.framework} / ${frontendCtx.httpClient} / ${frontendCtx.uiLibrary}`));
|
|
10966
11087
|
}
|
|
10967
11088
|
try {
|
|
10968
11089
|
const { dsl, specFile } = await runSingleRepoPipelineInWorkspace({
|
|
@@ -10979,22 +11100,22 @@ async function runMultiRepoPipeline(idea, workspace, opts, currentDir, config2)
|
|
|
10979
11100
|
});
|
|
10980
11101
|
if (repoReq.isContractProvider && dsl) {
|
|
10981
11102
|
contractDsls.set(repoReq.repoName, dsl);
|
|
10982
|
-
console.log(
|
|
11103
|
+
console.log(import_chalk19.default.green(` Contract stored for downstream repos.`));
|
|
10983
11104
|
}
|
|
10984
11105
|
results.push({ repoName: repoReq.repoName, status: "success", specFile, dsl, repoAbsPath, role: repoReq.role });
|
|
10985
|
-
console.log(
|
|
11106
|
+
console.log(import_chalk19.default.green(` \u2714 ${repoReq.repoName} complete`));
|
|
10986
11107
|
} catch (err) {
|
|
10987
|
-
console.error(
|
|
11108
|
+
console.error(import_chalk19.default.red(` \u2718 ${repoReq.repoName} failed: ${err.message}`));
|
|
10988
11109
|
results.push({ repoName: repoReq.repoName, status: "failed", specFile: null, dsl: null, repoAbsPath, role: repoReq.role });
|
|
10989
11110
|
}
|
|
10990
11111
|
}
|
|
10991
|
-
console.log(
|
|
10992
|
-
console.log(
|
|
10993
|
-
console.log(
|
|
11112
|
+
console.log(import_chalk19.default.bold.green("\n\u2714 Multi-repo pipeline complete!"));
|
|
11113
|
+
console.log(import_chalk19.default.gray(` Workspace: ${workspace.name}`));
|
|
11114
|
+
console.log(import_chalk19.default.gray(` Requirement: ${idea}`));
|
|
10994
11115
|
console.log();
|
|
10995
11116
|
for (const r of results) {
|
|
10996
|
-
const icon = r.status === "success" ?
|
|
10997
|
-
const specInfo = r.specFile ?
|
|
11117
|
+
const icon = r.status === "success" ? import_chalk19.default.green("\u2714") : r.status === "failed" ? import_chalk19.default.red("\u2718") : import_chalk19.default.gray("\u2212");
|
|
11118
|
+
const specInfo = r.specFile ? import_chalk19.default.gray(` \u2192 ${r.specFile}`) : "";
|
|
10998
11119
|
console.log(` ${icon} ${r.repoName} (${r.status})${specInfo}`);
|
|
10999
11120
|
}
|
|
11000
11121
|
return results;
|
|
@@ -11009,11 +11130,11 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
|
|
|
11009
11130
|
default: false
|
|
11010
11131
|
});
|
|
11011
11132
|
if (!overwrite) {
|
|
11012
|
-
console.log(
|
|
11133
|
+
console.log(import_chalk19.default.gray(" Cancelled."));
|
|
11013
11134
|
return;
|
|
11014
11135
|
}
|
|
11015
11136
|
}
|
|
11016
|
-
console.log(
|
|
11137
|
+
console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Workspace Setup \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"));
|
|
11017
11138
|
const workspaceName = await (0, import_prompts3.input)({
|
|
11018
11139
|
message: "Workspace name:",
|
|
11019
11140
|
validate: (v2) => v2.trim().length > 0 || "Name cannot be empty"
|
|
@@ -11027,11 +11148,11 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
|
|
|
11027
11148
|
const workspaceLoader = new WorkspaceLoader(currentDir);
|
|
11028
11149
|
const detected = await workspaceLoader.autoDetect();
|
|
11029
11150
|
if (detected.length === 0) {
|
|
11030
|
-
console.log(
|
|
11151
|
+
console.log(import_chalk19.default.yellow(" No recognizable repos found in sibling directories."));
|
|
11031
11152
|
} else {
|
|
11032
|
-
console.log(
|
|
11153
|
+
console.log(import_chalk19.default.cyan("\n Detected repos:"));
|
|
11033
11154
|
for (const r of detected) {
|
|
11034
|
-
console.log(
|
|
11155
|
+
console.log(import_chalk19.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
|
|
11035
11156
|
}
|
|
11036
11157
|
const keepAll = await (0, import_prompts3.confirm)({
|
|
11037
11158
|
message: `Include all ${detected.length} detected repo(s)?`,
|
|
@@ -11048,7 +11169,7 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
|
|
|
11048
11169
|
if (keep) repos.push(r);
|
|
11049
11170
|
}
|
|
11050
11171
|
}
|
|
11051
|
-
console.log(
|
|
11172
|
+
console.log(import_chalk19.default.green(` \u2714 ${repos.length} repo(s) added from auto-scan.`));
|
|
11052
11173
|
}
|
|
11053
11174
|
}
|
|
11054
11175
|
const repoTypeChoices = [
|
|
@@ -11070,7 +11191,7 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
|
|
|
11070
11191
|
default: repos.length === 0
|
|
11071
11192
|
});
|
|
11072
11193
|
while (addMore) {
|
|
11073
|
-
console.log(
|
|
11194
|
+
console.log(import_chalk19.default.cyan(`
|
|
11074
11195
|
Adding repo #${repos.length + 1}`));
|
|
11075
11196
|
const repoName = await (0, import_prompts3.input)({
|
|
11076
11197
|
message: "Repo name (e.g. api, web, app):",
|
|
@@ -11091,9 +11212,9 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
|
|
|
11091
11212
|
const { type, role } = await detectRepoType(absPath);
|
|
11092
11213
|
detectedType = type;
|
|
11093
11214
|
detectedRole = role;
|
|
11094
|
-
console.log(
|
|
11215
|
+
console.log(import_chalk19.default.gray(` Auto-detected: type=${type}, role=${role}`));
|
|
11095
11216
|
} else {
|
|
11096
|
-
console.log(
|
|
11217
|
+
console.log(import_chalk19.default.yellow(` Path "${absPath}" not found \u2014 type/role will be manual.`));
|
|
11097
11218
|
}
|
|
11098
11219
|
const repoType = await (0, import_prompts3.select)({
|
|
11099
11220
|
message: `Repo type for "${repoName}":`,
|
|
@@ -11116,53 +11237,53 @@ workspaceCmd.command("init").description(`Interactive workspace setup \u2014 cre
|
|
|
11116
11237
|
type: repoType,
|
|
11117
11238
|
role: repoRole
|
|
11118
11239
|
});
|
|
11119
|
-
console.log(
|
|
11240
|
+
console.log(import_chalk19.default.green(` \u2714 Added: ${repoName} (${repoRole}, ${repoType})`));
|
|
11120
11241
|
addMore = await (0, import_prompts3.confirm)({
|
|
11121
11242
|
message: "Add another repo?",
|
|
11122
11243
|
default: false
|
|
11123
11244
|
});
|
|
11124
11245
|
}
|
|
11125
11246
|
const workspaceConfig = { name: workspaceName, repos };
|
|
11126
|
-
console.log(
|
|
11127
|
-
console.log(
|
|
11247
|
+
console.log(import_chalk19.default.cyan("\n Workspace summary:"));
|
|
11248
|
+
console.log(import_chalk19.default.gray(` Name: ${workspaceName}`));
|
|
11128
11249
|
for (const r of repos) {
|
|
11129
|
-
console.log(
|
|
11250
|
+
console.log(import_chalk19.default.gray(` - ${r.name}: ${r.role} (${r.type}) at ${r.path}`));
|
|
11130
11251
|
}
|
|
11131
11252
|
const ok = await (0, import_prompts3.confirm)({ message: `Save to ${WORKSPACE_CONFIG_FILE}?`, default: true });
|
|
11132
11253
|
if (!ok) {
|
|
11133
|
-
console.log(
|
|
11254
|
+
console.log(import_chalk19.default.gray(" Cancelled."));
|
|
11134
11255
|
return;
|
|
11135
11256
|
}
|
|
11136
11257
|
const loader = new WorkspaceLoader(currentDir);
|
|
11137
11258
|
const saved = await loader.save(workspaceConfig);
|
|
11138
|
-
console.log(
|
|
11259
|
+
console.log(import_chalk19.default.green(`
|
|
11139
11260
|
\u2714 Workspace saved: ${saved}`));
|
|
11140
|
-
console.log(
|
|
11261
|
+
console.log(import_chalk19.default.gray(` Run \`ai-spec create "your feature"\` \u2014 workspace mode will activate automatically.`));
|
|
11141
11262
|
});
|
|
11142
11263
|
workspaceCmd.command("status").description("Show current workspace configuration").action(async () => {
|
|
11143
11264
|
const currentDir = process.cwd();
|
|
11144
11265
|
const loader = new WorkspaceLoader(currentDir);
|
|
11145
11266
|
const config2 = await loader.load();
|
|
11146
11267
|
if (!config2) {
|
|
11147
|
-
console.log(
|
|
11148
|
-
console.log(
|
|
11268
|
+
console.log(import_chalk19.default.yellow(`No ${WORKSPACE_CONFIG_FILE} found in ${currentDir}`));
|
|
11269
|
+
console.log(import_chalk19.default.gray(" Run `ai-spec workspace init` to create one."));
|
|
11149
11270
|
return;
|
|
11150
11271
|
}
|
|
11151
|
-
console.log(
|
|
11272
|
+
console.log(import_chalk19.default.bold(`
|
|
11152
11273
|
Workspace: ${config2.name}`));
|
|
11153
|
-
console.log(
|
|
11154
|
-
console.log(
|
|
11274
|
+
console.log(import_chalk19.default.gray(` Config: ${path22.join(currentDir, WORKSPACE_CONFIG_FILE)}`));
|
|
11275
|
+
console.log(import_chalk19.default.gray(` Repos (${config2.repos.length}):
|
|
11155
11276
|
`));
|
|
11156
11277
|
for (const repo of config2.repos) {
|
|
11157
11278
|
const absPath = loader.resolveAbsPath(repo);
|
|
11158
11279
|
const exists = await fs23.pathExists(absPath);
|
|
11159
|
-
const status = exists ?
|
|
11280
|
+
const status = exists ? import_chalk19.default.green("found") : import_chalk19.default.red("not found");
|
|
11160
11281
|
console.log(
|
|
11161
|
-
` ${
|
|
11282
|
+
` ${import_chalk19.default.bold(repo.name.padEnd(12))} ${repo.role.padEnd(10)} ${repo.type.padEnd(16)} ${status}`
|
|
11162
11283
|
);
|
|
11163
|
-
console.log(
|
|
11284
|
+
console.log(import_chalk19.default.gray(` path: ${absPath}`));
|
|
11164
11285
|
if (repo.constitution) {
|
|
11165
|
-
console.log(
|
|
11286
|
+
console.log(import_chalk19.default.green(` constitution: found`));
|
|
11166
11287
|
}
|
|
11167
11288
|
}
|
|
11168
11289
|
});
|
|
@@ -11179,30 +11300,30 @@ program.command("update").description("Update an existing spec with a change req
|
|
|
11179
11300
|
const modelName = opts.model || config2.model || DEFAULT_MODELS[providerName];
|
|
11180
11301
|
const apiKey = await resolveApiKey(providerName, opts.key);
|
|
11181
11302
|
const provider = createProvider(providerName, apiKey, modelName);
|
|
11182
|
-
console.log(
|
|
11183
|
-
console.log(
|
|
11303
|
+
console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec update \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"));
|
|
11304
|
+
console.log(import_chalk19.default.gray(` Provider: ${providerName}/${modelName}`));
|
|
11184
11305
|
const updateRunId = generateRunId();
|
|
11185
11306
|
const updateSnapshot = new RunSnapshot(currentDir, updateRunId);
|
|
11186
11307
|
setActiveSnapshot(updateSnapshot);
|
|
11187
11308
|
const updateLogger = new RunLogger(currentDir, updateRunId, { provider: providerName, model: modelName });
|
|
11188
11309
|
setActiveLogger(updateLogger);
|
|
11189
|
-
console.log(
|
|
11310
|
+
console.log(import_chalk19.default.gray(` Run ID: ${updateRunId}`));
|
|
11190
11311
|
let specPath = opts.spec ?? null;
|
|
11191
11312
|
if (!specPath) {
|
|
11192
11313
|
const specsDir = path22.join(currentDir, "specs");
|
|
11193
11314
|
const latest = await SpecUpdater.findLatestSpec(specsDir);
|
|
11194
11315
|
if (!latest) {
|
|
11195
|
-
console.error(
|
|
11316
|
+
console.error(import_chalk19.default.red(" No spec files found in specs/. Run `ai-spec create` first or use --spec <path>."));
|
|
11196
11317
|
process.exit(1);
|
|
11197
11318
|
}
|
|
11198
11319
|
specPath = latest.filePath;
|
|
11199
|
-
console.log(
|
|
11320
|
+
console.log(import_chalk19.default.gray(` Using spec: ${path22.relative(currentDir, specPath)} (v${latest.version})`));
|
|
11200
11321
|
}
|
|
11201
|
-
console.log(
|
|
11322
|
+
console.log(import_chalk19.default.gray(" Loading project context..."));
|
|
11202
11323
|
const loader = new ContextLoader(currentDir);
|
|
11203
11324
|
const context = await loader.loadProjectContext();
|
|
11204
11325
|
if (context.constitution && context.constitution.length > 6e3) {
|
|
11205
|
-
console.log(
|
|
11326
|
+
console.log(import_chalk19.default.yellow(` \u26A0 Constitution is long (${context.constitution.length.toLocaleString()} chars). Consider running: ai-spec init --consolidate`));
|
|
11206
11327
|
}
|
|
11207
11328
|
const { detectRepoType: _detectRepoType } = await Promise.resolve().then(() => (init_workspace_loader(), workspace_loader_exports));
|
|
11208
11329
|
const { type: repoType } = await _detectRepoType(currentDir);
|
|
@@ -11214,19 +11335,19 @@ program.command("update").description("Update an existing spec with a change req
|
|
|
11214
11335
|
repoType
|
|
11215
11336
|
});
|
|
11216
11337
|
} catch (err) {
|
|
11217
|
-
console.error(
|
|
11338
|
+
console.error(import_chalk19.default.red(` Update failed: ${err.message}`));
|
|
11218
11339
|
process.exit(1);
|
|
11219
11340
|
}
|
|
11220
|
-
console.log(
|
|
11341
|
+
console.log(import_chalk19.default.green(`
|
|
11221
11342
|
\u2714 Spec updated \u2192 v${result.newVersion}: ${path22.relative(currentDir, result.newSpecPath)}`));
|
|
11222
11343
|
if (result.newDslPath) {
|
|
11223
|
-
console.log(
|
|
11344
|
+
console.log(import_chalk19.default.green(` \u2714 DSL updated: ${path22.relative(currentDir, result.newDslPath)}`));
|
|
11224
11345
|
}
|
|
11225
11346
|
if (result.affectedFiles.length > 0) {
|
|
11226
|
-
console.log(
|
|
11347
|
+
console.log(import_chalk19.default.cyan("\n Affected files:"));
|
|
11227
11348
|
for (const f of result.affectedFiles) {
|
|
11228
|
-
const icon = f.action === "create" ?
|
|
11229
|
-
console.log(` ${icon} ${f.file}: ${
|
|
11349
|
+
const icon = f.action === "create" ? import_chalk19.default.green("+") : import_chalk19.default.yellow("~");
|
|
11350
|
+
console.log(` ${icon} ${f.file}: ${import_chalk19.default.gray(f.description)}`);
|
|
11230
11351
|
}
|
|
11231
11352
|
}
|
|
11232
11353
|
if (opts.codegen && result.affectedFiles.length > 0) {
|
|
@@ -11234,7 +11355,7 @@ program.command("update").description("Update an existing spec with a change req
|
|
|
11234
11355
|
const codegenModelName = opts.codegenModel || config2.codegenModel || DEFAULT_MODELS[codegenProviderName];
|
|
11235
11356
|
const codegenApiKey = opts.codegenKey ?? (codegenProviderName === providerName ? apiKey : await resolveApiKey(codegenProviderName, opts.codegenKey));
|
|
11236
11357
|
const codegenProvider = createProvider(codegenProviderName, codegenApiKey, codegenModelName);
|
|
11237
|
-
console.log(
|
|
11358
|
+
console.log(import_chalk19.default.blue("\n Regenerating affected files..."));
|
|
11238
11359
|
const codeGenerator = new CodeGenerator(codegenProvider, "api");
|
|
11239
11360
|
const specContent = await fs23.readFile(result.newSpecPath, "utf-8");
|
|
11240
11361
|
const constitutionSection = context.constitution ? `
|
|
@@ -11264,7 +11385,7 @@ ${specContent}
|
|
|
11264
11385
|
${constitutionSection}${dslSection}
|
|
11265
11386
|
=== ${existing ? "Current File (return the FULL updated content)" : "New File"} ===
|
|
11266
11387
|
${existing || "Create from scratch."}`;
|
|
11267
|
-
process.stdout.write(` ${existing ?
|
|
11388
|
+
process.stdout.write(` ${existing ? import_chalk19.default.yellow("~") : import_chalk19.default.green("+")} ${affected.file}... `);
|
|
11268
11389
|
try {
|
|
11269
11390
|
const { getCodeGenSystemPrompt: _getPrompt } = await Promise.resolve().then(() => (init_codegen_prompt(), codegen_prompt_exports));
|
|
11270
11391
|
const raw = await codegenProvider.generate(codePrompt, _getPrompt(repoType));
|
|
@@ -11273,10 +11394,10 @@ ${existing || "Create from scratch."}`;
|
|
|
11273
11394
|
await updateSnapshot.snapshotFile(fullPath);
|
|
11274
11395
|
await fs23.writeFile(fullPath, content, "utf-8");
|
|
11275
11396
|
updateLogger.fileWritten(affected.file);
|
|
11276
|
-
console.log(
|
|
11397
|
+
console.log(import_chalk19.default.green("\u2714"));
|
|
11277
11398
|
} catch (err) {
|
|
11278
11399
|
updateLogger.stageFail("update_codegen", `${affected.file}: ${err.message}`);
|
|
11279
|
-
console.log(
|
|
11400
|
+
console.log(import_chalk19.default.red(`\u2718 ${err.message}`));
|
|
11280
11401
|
}
|
|
11281
11402
|
}
|
|
11282
11403
|
updateLogger.stageEnd("update_codegen", { filesUpdated: result.affectedFiles.length });
|
|
@@ -11292,13 +11413,13 @@ ${existing || "Create from scratch."}`;
|
|
|
11292
11413
|
updateLogger.finish();
|
|
11293
11414
|
updateLogger.printSummary();
|
|
11294
11415
|
if (updateSnapshot.fileCount > 0) {
|
|
11295
|
-
console.log(
|
|
11416
|
+
console.log(import_chalk19.default.gray(` To undo changes: ai-spec restore ${updateRunId}`));
|
|
11296
11417
|
}
|
|
11297
11418
|
if (!opts.codegen && result.affectedFiles.length > 0) {
|
|
11298
|
-
console.log(
|
|
11299
|
-
console.log(
|
|
11300
|
-
console.log(
|
|
11301
|
-
console.log(
|
|
11419
|
+
console.log(import_chalk19.default.blue("\n Next steps:"));
|
|
11420
|
+
console.log(import_chalk19.default.gray(` \u2022 Re-run with --codegen to regenerate affected files automatically`));
|
|
11421
|
+
console.log(import_chalk19.default.gray(` \u2022 Or update files manually based on the affected files list above`));
|
|
11422
|
+
console.log(import_chalk19.default.gray(` \u2022 Run \`ai-spec mock\` to refresh the mock server with the new DSL`));
|
|
11302
11423
|
}
|
|
11303
11424
|
});
|
|
11304
11425
|
program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (YAML or JSON)").option("--openapi", "Export as OpenAPI 3.1.0 (default behaviour)").option("--format <fmt>", "Output format: yaml | json (default: yaml)", "yaml").option("--output <path>", "Output file path (default: openapi.yaml)").option("--server <url>", "API server URL in the OpenAPI document (default: http://localhost:3000)").option("--dsl <path>", "Path to a specific .dsl.json file (auto-detected if omitted)").action(async (opts) => {
|
|
@@ -11307,19 +11428,19 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
|
|
|
11307
11428
|
if (!dslPath) {
|
|
11308
11429
|
dslPath = await findLatestDslFile(currentDir);
|
|
11309
11430
|
if (!dslPath) {
|
|
11310
|
-
console.error(
|
|
11431
|
+
console.error(import_chalk19.default.red(" No .dsl.json file found. Run `ai-spec create` first or use --dsl <path>."));
|
|
11311
11432
|
process.exit(1);
|
|
11312
11433
|
}
|
|
11313
|
-
console.log(
|
|
11434
|
+
console.log(import_chalk19.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
|
|
11314
11435
|
}
|
|
11315
11436
|
let dsl;
|
|
11316
11437
|
try {
|
|
11317
11438
|
dsl = await fs23.readJson(dslPath);
|
|
11318
11439
|
} catch (err) {
|
|
11319
|
-
console.error(
|
|
11440
|
+
console.error(import_chalk19.default.red(` Failed to read DSL: ${err.message}`));
|
|
11320
11441
|
process.exit(1);
|
|
11321
11442
|
}
|
|
11322
|
-
console.log(
|
|
11443
|
+
console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec export \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"));
|
|
11323
11444
|
const format = opts.format === "json" ? "json" : "yaml";
|
|
11324
11445
|
const serverUrl = opts.server || "http://localhost:3000";
|
|
11325
11446
|
try {
|
|
@@ -11329,30 +11450,30 @@ program.command("export").description("Export the latest DSL to OpenAPI 3.1.0 (Y
|
|
|
11329
11450
|
outputPath: opts.output
|
|
11330
11451
|
});
|
|
11331
11452
|
const rel = path22.relative(currentDir, outputPath);
|
|
11332
|
-
console.log(
|
|
11333
|
-
console.log(
|
|
11334
|
-
console.log(
|
|
11335
|
-
console.log(
|
|
11336
|
-
console.log(
|
|
11337
|
-
console.log(
|
|
11338
|
-
console.log(
|
|
11339
|
-
console.log(
|
|
11453
|
+
console.log(import_chalk19.default.green(` \u2714 OpenAPI ${format.toUpperCase()} exported: ${rel}`));
|
|
11454
|
+
console.log(import_chalk19.default.gray(` Feature : ${dsl.feature.title}`));
|
|
11455
|
+
console.log(import_chalk19.default.gray(` Endpoints: ${dsl.endpoints.length}`));
|
|
11456
|
+
console.log(import_chalk19.default.gray(` Models : ${dsl.models.length}`));
|
|
11457
|
+
console.log(import_chalk19.default.gray(` Server : ${serverUrl}`));
|
|
11458
|
+
console.log(import_chalk19.default.blue("\n Next steps:"));
|
|
11459
|
+
console.log(import_chalk19.default.gray(` \u2022 Import ${rel} into Postman / Insomnia / Swagger UI`));
|
|
11460
|
+
console.log(import_chalk19.default.gray(` \u2022 Use openapi-generator to generate client SDKs`));
|
|
11340
11461
|
} catch (err) {
|
|
11341
|
-
console.error(
|
|
11462
|
+
console.error(import_chalk19.default.red(` Export failed: ${err.message}`));
|
|
11342
11463
|
process.exit(1);
|
|
11343
11464
|
}
|
|
11344
11465
|
});
|
|
11345
11466
|
program.command("mock").description("Generate a standalone mock server + proxy config from the latest DSL").option("--port <n>", "Mock server port (default: 3001)", "3001").option("--msw", "Also generate MSW (Mock Service Worker) handlers at src/mocks/").option("--proxy", "Also generate frontend proxy config snippet").option("--dsl <path>", "Path to a specific .dsl.json file (auto-detected if omitted)").option("--workspace", "Generate mock assets for all backend repos in the workspace").option("--serve", "Start mock server in background + patch frontend proxy (use with --frontend)").option("--frontend <path>", "Path to frontend project for proxy patching (used with --serve/--restore)").option("--restore", "Undo proxy changes and stop mock server (requires --frontend or auto-detects)").action(async (opts) => {
|
|
11346
11467
|
const currentDir = process.cwd();
|
|
11347
11468
|
const port = parseInt(opts.port, 10) || 3001;
|
|
11348
|
-
console.log(
|
|
11469
|
+
console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 ai-spec mock \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"));
|
|
11349
11470
|
if (opts.restore) {
|
|
11350
11471
|
const frontendDir = opts.frontend ? path22.resolve(opts.frontend) : currentDir;
|
|
11351
11472
|
const r = await restoreMockProxy(frontendDir);
|
|
11352
11473
|
if (r.restored) {
|
|
11353
|
-
console.log(
|
|
11474
|
+
console.log(import_chalk19.default.green(" \u2714 Proxy restored and mock server stopped."));
|
|
11354
11475
|
} else {
|
|
11355
|
-
console.log(
|
|
11476
|
+
console.log(import_chalk19.default.yellow(` ${r.note ?? "Nothing to restore."}`));
|
|
11356
11477
|
}
|
|
11357
11478
|
return;
|
|
11358
11479
|
}
|
|
@@ -11360,21 +11481,21 @@ program.command("mock").description("Generate a standalone mock server + proxy c
|
|
|
11360
11481
|
const workspaceLoader = new WorkspaceLoader(currentDir);
|
|
11361
11482
|
const workspaceConfig = await workspaceLoader.load();
|
|
11362
11483
|
if (!workspaceConfig) {
|
|
11363
|
-
console.error(
|
|
11484
|
+
console.error(import_chalk19.default.red(` No ${WORKSPACE_CONFIG_FILE} found. Run \`ai-spec workspace init\` first.`));
|
|
11364
11485
|
process.exit(1);
|
|
11365
11486
|
}
|
|
11366
11487
|
const backendRepos = workspaceConfig.repos.filter((r) => r.role === "backend");
|
|
11367
11488
|
if (backendRepos.length === 0) {
|
|
11368
|
-
console.log(
|
|
11489
|
+
console.log(import_chalk19.default.yellow(" No backend repos found in workspace."));
|
|
11369
11490
|
return;
|
|
11370
11491
|
}
|
|
11371
11492
|
for (const repo of backendRepos) {
|
|
11372
11493
|
const repoAbsPath = workspaceLoader.resolveAbsPath(repo);
|
|
11373
|
-
console.log(
|
|
11494
|
+
console.log(import_chalk19.default.cyan(`
|
|
11374
11495
|
Repo: ${repo.name} (${repoAbsPath})`));
|
|
11375
11496
|
const dslFile = await findLatestDslFile(repoAbsPath);
|
|
11376
11497
|
if (!dslFile) {
|
|
11377
|
-
console.log(
|
|
11498
|
+
console.log(import_chalk19.default.yellow(` No DSL file found \u2014 skipping.`));
|
|
11378
11499
|
continue;
|
|
11379
11500
|
}
|
|
11380
11501
|
const dsl2 = await fs23.readJson(dslFile);
|
|
@@ -11384,8 +11505,8 @@ program.command("mock").description("Generate a standalone mock server + proxy c
|
|
|
11384
11505
|
proxy: opts.proxy
|
|
11385
11506
|
});
|
|
11386
11507
|
for (const f of result2.files) {
|
|
11387
|
-
console.log(
|
|
11388
|
-
console.log(
|
|
11508
|
+
console.log(import_chalk19.default.green(` \u2714 ${f.path}`));
|
|
11509
|
+
console.log(import_chalk19.default.gray(` ${f.description}`));
|
|
11389
11510
|
}
|
|
11390
11511
|
}
|
|
11391
11512
|
return;
|
|
@@ -11395,19 +11516,19 @@ program.command("mock").description("Generate a standalone mock server + proxy c
|
|
|
11395
11516
|
dslPath = await findLatestDslFile(currentDir);
|
|
11396
11517
|
if (!dslPath) {
|
|
11397
11518
|
console.error(
|
|
11398
|
-
|
|
11519
|
+
import_chalk19.default.red(
|
|
11399
11520
|
" No .dsl.json file found in .ai-spec/. Run `ai-spec create` first or use --dsl <path>."
|
|
11400
11521
|
)
|
|
11401
11522
|
);
|
|
11402
11523
|
process.exit(1);
|
|
11403
11524
|
}
|
|
11404
|
-
console.log(
|
|
11525
|
+
console.log(import_chalk19.default.gray(` Using DSL: ${path22.relative(currentDir, dslPath)}`));
|
|
11405
11526
|
}
|
|
11406
11527
|
let dsl;
|
|
11407
11528
|
try {
|
|
11408
11529
|
dsl = await fs23.readJson(dslPath);
|
|
11409
11530
|
} catch (err) {
|
|
11410
|
-
console.error(
|
|
11531
|
+
console.error(import_chalk19.default.red(` Failed to read DSL file: ${err.message}`));
|
|
11411
11532
|
process.exit(1);
|
|
11412
11533
|
}
|
|
11413
11534
|
const result = await generateMockAssets(dsl, currentDir, {
|
|
@@ -11415,58 +11536,58 @@ program.command("mock").description("Generate a standalone mock server + proxy c
|
|
|
11415
11536
|
msw: opts.msw,
|
|
11416
11537
|
proxy: opts.proxy
|
|
11417
11538
|
});
|
|
11418
|
-
console.log(
|
|
11539
|
+
console.log(import_chalk19.default.green(`
|
|
11419
11540
|
\u2714 Mock assets generated (${result.files.length} file(s)):`));
|
|
11420
11541
|
for (const f of result.files) {
|
|
11421
|
-
console.log(
|
|
11422
|
-
console.log(
|
|
11542
|
+
console.log(import_chalk19.default.green(` ${f.path}`));
|
|
11543
|
+
console.log(import_chalk19.default.gray(` ${f.description}`));
|
|
11423
11544
|
}
|
|
11424
11545
|
if (opts.serve) {
|
|
11425
11546
|
const serverJsPath = path22.join(currentDir, "mock", "server.js");
|
|
11426
11547
|
if (!await fs23.pathExists(serverJsPath)) {
|
|
11427
|
-
console.error(
|
|
11548
|
+
console.error(import_chalk19.default.red(" mock/server.js not found \u2014 generation may have failed."));
|
|
11428
11549
|
process.exit(1);
|
|
11429
11550
|
}
|
|
11430
11551
|
const pid = startMockServerBackground(serverJsPath, port);
|
|
11431
|
-
console.log(
|
|
11552
|
+
console.log(import_chalk19.default.green(`
|
|
11432
11553
|
\u2714 Mock server started (PID ${pid}) \u2192 http://localhost:${port}`));
|
|
11433
11554
|
if (opts.frontend) {
|
|
11434
11555
|
const frontendDir = path22.resolve(opts.frontend);
|
|
11435
11556
|
const proxyResult = await applyMockProxy(frontendDir, port, dsl.endpoints);
|
|
11436
11557
|
await saveMockServerPid(frontendDir, pid);
|
|
11437
11558
|
if (proxyResult.applied) {
|
|
11438
|
-
console.log(
|
|
11439
|
-
console.log(
|
|
11559
|
+
console.log(import_chalk19.default.green(` \u2714 Frontend proxy patched (${proxyResult.framework})`));
|
|
11560
|
+
console.log(import_chalk19.default.bold.cyan(`
|
|
11440
11561
|
Ready! Open a new terminal and run:`));
|
|
11441
|
-
console.log(
|
|
11442
|
-
console.log(
|
|
11443
|
-
console.log(
|
|
11562
|
+
console.log(import_chalk19.default.white(` cd ${frontendDir}`));
|
|
11563
|
+
console.log(import_chalk19.default.white(` ${proxyResult.devCommand}`));
|
|
11564
|
+
console.log(import_chalk19.default.gray(`
|
|
11444
11565
|
When done: ai-spec mock --restore --frontend ${frontendDir}`));
|
|
11445
11566
|
} else {
|
|
11446
|
-
console.log(
|
|
11447
|
-
if (proxyResult.note) console.log(
|
|
11567
|
+
console.log(import_chalk19.default.yellow(` \u26A0 Auto-patch not available for ${proxyResult.framework}.`));
|
|
11568
|
+
if (proxyResult.note) console.log(import_chalk19.default.gray(` ${proxyResult.note}`));
|
|
11448
11569
|
}
|
|
11449
11570
|
} else {
|
|
11450
|
-
console.log(
|
|
11451
|
-
console.log(
|
|
11571
|
+
console.log(import_chalk19.default.gray(` Tip: use --frontend <path> to also auto-patch your frontend proxy config.`));
|
|
11572
|
+
console.log(import_chalk19.default.gray(` Mock server: http://localhost:${port}`));
|
|
11452
11573
|
}
|
|
11453
11574
|
return;
|
|
11454
11575
|
}
|
|
11455
|
-
console.log(
|
|
11456
|
-
console.log(
|
|
11457
|
-
console.log(
|
|
11458
|
-
console.log(
|
|
11459
|
-
console.log(
|
|
11460
|
-
console.log(
|
|
11461
|
-
console.log(
|
|
11462
|
-
console.log(
|
|
11576
|
+
console.log(import_chalk19.default.blue("\n\u2500\u2500\u2500 Quick start \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"));
|
|
11577
|
+
console.log(import_chalk19.default.white(` 1. Install express (if not already):`));
|
|
11578
|
+
console.log(import_chalk19.default.gray(` npm install --save-dev express`));
|
|
11579
|
+
console.log(import_chalk19.default.white(` 2. Start mock server:`));
|
|
11580
|
+
console.log(import_chalk19.default.gray(` node mock/server.js`));
|
|
11581
|
+
console.log(import_chalk19.default.gray(` # or: ai-spec mock --serve --frontend <path-to-frontend>`));
|
|
11582
|
+
console.log(import_chalk19.default.white(` 3. Configure your frontend to proxy API calls to:`));
|
|
11583
|
+
console.log(import_chalk19.default.gray(` http://localhost:${port}`));
|
|
11463
11584
|
if (opts.proxy) {
|
|
11464
|
-
console.log(
|
|
11585
|
+
console.log(import_chalk19.default.gray(` (See the generated proxy config file for framework-specific instructions)`));
|
|
11465
11586
|
}
|
|
11466
11587
|
if (opts.msw) {
|
|
11467
|
-
console.log(
|
|
11468
|
-
console.log(
|
|
11469
|
-
console.log(
|
|
11588
|
+
console.log(import_chalk19.default.white(` 4. MSW: import and start the worker in your app entry:`));
|
|
11589
|
+
console.log(import_chalk19.default.gray(` import { worker } from './mocks/browser';`));
|
|
11590
|
+
console.log(import_chalk19.default.gray(` if (process.env.NODE_ENV === 'development') worker.start();`));
|
|
11470
11591
|
}
|
|
11471
11592
|
});
|
|
11472
11593
|
program.command("learn").description("Append a lesson or engineering decision directly to constitution \xA79").argument("[lesson]", "The lesson or decision to record (prompted if omitted)").action(async (lesson) => {
|
|
@@ -11482,24 +11603,24 @@ program.command("learn").description("Append a lesson or engineering decision di
|
|
|
11482
11603
|
}
|
|
11483
11604
|
const result = await appendDirectLesson(currentDir, lesson.trim());
|
|
11484
11605
|
if (result.appended) {
|
|
11485
|
-
console.log(
|
|
11606
|
+
console.log(import_chalk19.default.green(`
|
|
11486
11607
|
\u2714 Lesson appended to constitution \xA79`));
|
|
11487
|
-
console.log(
|
|
11608
|
+
console.log(import_chalk19.default.gray(` File: .ai-spec-constitution.md`));
|
|
11488
11609
|
} else {
|
|
11489
|
-
console.log(
|
|
11610
|
+
console.log(import_chalk19.default.yellow(`
|
|
11490
11611
|
\u26A0 Not appended: ${result.reason}`));
|
|
11491
11612
|
}
|
|
11492
11613
|
});
|
|
11493
11614
|
program.command("restore").description("Restore files modified by a previous run").argument("<runId>", "Run ID shown at the end of a create / generate run").action(async (runId) => {
|
|
11494
11615
|
const currentDir = process.cwd();
|
|
11495
11616
|
const snapshot = new RunSnapshot(currentDir, runId);
|
|
11496
|
-
console.log(
|
|
11617
|
+
console.log(import_chalk19.default.blue(`Restoring run: ${runId}...`));
|
|
11497
11618
|
const restored = await snapshot.restore();
|
|
11498
11619
|
if (restored.length === 0) {
|
|
11499
|
-
console.log(
|
|
11620
|
+
console.log(import_chalk19.default.yellow(" No backup found for this run ID."));
|
|
11500
11621
|
} else {
|
|
11501
|
-
restored.forEach((f) => console.log(
|
|
11502
|
-
console.log(
|
|
11622
|
+
restored.forEach((f) => console.log(import_chalk19.default.green(` \u2714 restored: ${f}`)));
|
|
11623
|
+
console.log(import_chalk19.default.bold.green(`
|
|
11503
11624
|
\u2714 ${restored.length} file(s) restored.`));
|
|
11504
11625
|
}
|
|
11505
11626
|
});
|