@ryuenn3123/agentic-senior-core 1.8.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/.agent-context/blueprints/api-nextjs.md +184 -0
- package/.agent-context/blueprints/aspnet-api.md +247 -0
- package/.agent-context/blueprints/ci-github-actions.md +226 -0
- package/.agent-context/blueprints/ci-gitlab.md +200 -0
- package/.agent-context/blueprints/fastapi-service.md +210 -0
- package/.agent-context/blueprints/go-service.md +217 -0
- package/.agent-context/blueprints/graphql-grpc-api.md +51 -0
- package/.agent-context/blueprints/infrastructure-as-code.md +62 -0
- package/.agent-context/blueprints/kubernetes-manifests.md +76 -0
- package/.agent-context/blueprints/laravel-api.md +223 -0
- package/.agent-context/blueprints/nestjs-logic.md +247 -0
- package/.agent-context/blueprints/observability.md +227 -0
- package/.agent-context/blueprints/spring-boot-api.md +218 -0
- package/.agent-context/policies/llm-judge-threshold.json +20 -0
- package/.agent-context/profiles/platform.md +13 -0
- package/.agent-context/profiles/regulated.md +13 -0
- package/.agent-context/profiles/startup.md +13 -0
- package/.agent-context/prompts/init-project.md +86 -0
- package/.agent-context/prompts/refactor.md +45 -0
- package/.agent-context/prompts/review-code.md +47 -0
- package/.agent-context/review-checklists/architecture-review.md +70 -0
- package/.agent-context/review-checklists/frontend-usability.md +33 -0
- package/.agent-context/review-checklists/performance-audit.md +65 -0
- package/.agent-context/review-checklists/pr-checklist.md +97 -0
- package/.agent-context/review-checklists/release-operations.md +29 -0
- package/.agent-context/review-checklists/security-audit.md +113 -0
- package/.agent-context/rules/api-docs.md +186 -0
- package/.agent-context/rules/architecture.md +198 -0
- package/.agent-context/rules/database-design.md +202 -0
- package/.agent-context/rules/efficiency-vs-hype.md +143 -0
- package/.agent-context/rules/error-handling.md +234 -0
- package/.agent-context/rules/event-driven.md +226 -0
- package/.agent-context/rules/frontend-architecture.md +66 -0
- package/.agent-context/rules/git-workflow.md +200 -0
- package/.agent-context/rules/microservices.md +174 -0
- package/.agent-context/rules/naming-conv.md +141 -0
- package/.agent-context/rules/performance.md +168 -0
- package/.agent-context/rules/realtime.md +47 -0
- package/.agent-context/rules/security.md +195 -0
- package/.agent-context/rules/testing.md +178 -0
- package/.agent-context/stacks/csharp.md +149 -0
- package/.agent-context/stacks/go.md +181 -0
- package/.agent-context/stacks/java.md +135 -0
- package/.agent-context/stacks/php.md +178 -0
- package/.agent-context/stacks/python.md +153 -0
- package/.agent-context/stacks/ruby.md +80 -0
- package/.agent-context/stacks/rust.md +86 -0
- package/.agent-context/stacks/typescript.md +317 -0
- package/.agent-context/state/architecture-map.md +25 -0
- package/.agent-context/state/dependency-map.md +32 -0
- package/.agent-override.md +36 -0
- package/.agents/workflows/init-project.md +29 -0
- package/.agents/workflows/refactor.md +29 -0
- package/.agents/workflows/review-code.md +29 -0
- package/.cursorrules +140 -0
- package/.gemini/instructions.md +97 -0
- package/.github/ISSUE_TEMPLATE/v1.7-frontend-work-item.yml +54 -0
- package/.github/copilot-instructions.md +104 -0
- package/.github/workflows/benchmark-detection.yml +38 -0
- package/.github/workflows/frontend-usability-gate.yml +36 -0
- package/.github/workflows/release-gate.yml +32 -0
- package/.github/workflows/sbom-compliance.yml +32 -0
- package/.windsurfrules +106 -0
- package/AGENTS.md +131 -0
- package/CONTRIBUTING.md +136 -0
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/bin/agentic-senior-core.js +1147 -0
- package/mcp.json +29 -0
- package/package.json +50 -0
- package/scripts/detection-benchmark.mjs +138 -0
- package/scripts/frontend-usability-audit.mjs +87 -0
- package/scripts/generate-sbom.mjs +61 -0
- package/scripts/init-project.ps1 +105 -0
- package/scripts/init-project.sh +131 -0
- package/scripts/llm-judge.mjs +664 -0
- package/scripts/release-gate.mjs +116 -0
- package/scripts/validate.mjs +554 -0
|
@@ -0,0 +1,1147 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agentic-Senior-Core CLI (V1.7)
|
|
4
|
+
*
|
|
5
|
+
* Newbie-first delivery engine for bootstrapping governance files with
|
|
6
|
+
* profile selection, stack auto-detection, and plain-language summaries.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require("node:fs/promises");
|
|
10
|
+
const fsSync = require("node:fs");
|
|
11
|
+
const path = require("node:path");
|
|
12
|
+
const readline = require("node:readline/promises");
|
|
13
|
+
const { stdin: input, stdout: output, exit } = require("node:process");
|
|
14
|
+
|
|
15
|
+
const REPO_ROOT = path.resolve(__dirname, "..");
|
|
16
|
+
const PACKAGE_JSON_PATH = path.join(REPO_ROOT, "package.json");
|
|
17
|
+
const CLI_VERSION = JSON.parse(fsSync.readFileSync(PACKAGE_JSON_PATH, "utf8")).version;
|
|
18
|
+
const AGENT_CONTEXT_DIR = path.join(REPO_ROOT, ".agent-context");
|
|
19
|
+
const POLICY_FILE_NAME = "llm-judge-threshold.json";
|
|
20
|
+
const PROFILE_PACKS_DIRECTORY_NAME = "profiles";
|
|
21
|
+
const PROFILE_PACK_REQUIRED_FIELDS = [
|
|
22
|
+
"slug",
|
|
23
|
+
"displayName",
|
|
24
|
+
"description",
|
|
25
|
+
"defaultProfile",
|
|
26
|
+
"defaultStack",
|
|
27
|
+
"defaultBlueprint",
|
|
28
|
+
"ciGuardrails",
|
|
29
|
+
"lockCi",
|
|
30
|
+
"blockingSeverities",
|
|
31
|
+
];
|
|
32
|
+
const ALLOWED_SEVERITY_LEVELS = new Set(["critical", "high", "medium", "low"]);
|
|
33
|
+
const BLUEPRINT_RECOMMENDATIONS = {
|
|
34
|
+
"typescript.md": "api-nextjs.md",
|
|
35
|
+
"python.md": "fastapi-service.md",
|
|
36
|
+
"java.md": "spring-boot-api.md",
|
|
37
|
+
"php.md": "laravel-api.md",
|
|
38
|
+
"go.md": "go-service.md",
|
|
39
|
+
"csharp.md": "aspnet-api.md",
|
|
40
|
+
};
|
|
41
|
+
const PROFILE_PRESETS = {
|
|
42
|
+
beginner: {
|
|
43
|
+
displayName: "Beginner",
|
|
44
|
+
description: "Safest path. Minimal decisions, TypeScript defaults, and CI enabled.",
|
|
45
|
+
defaultStackFileName: "typescript.md",
|
|
46
|
+
defaultBlueprintFileName: "api-nextjs.md",
|
|
47
|
+
defaultCi: true,
|
|
48
|
+
lockCi: true,
|
|
49
|
+
blockingSeverities: ["critical"],
|
|
50
|
+
},
|
|
51
|
+
balanced: {
|
|
52
|
+
displayName: "Balanced",
|
|
53
|
+
description: "Recommended for most teams. Guided choices with strong default guardrails.",
|
|
54
|
+
defaultStackFileName: null,
|
|
55
|
+
defaultBlueprintFileName: null,
|
|
56
|
+
defaultCi: true,
|
|
57
|
+
lockCi: false,
|
|
58
|
+
blockingSeverities: ["critical", "high"],
|
|
59
|
+
},
|
|
60
|
+
strict: {
|
|
61
|
+
displayName: "Strict",
|
|
62
|
+
description: "Tighter governance. CI stays on and medium-risk findings can block merges.",
|
|
63
|
+
defaultStackFileName: null,
|
|
64
|
+
defaultBlueprintFileName: null,
|
|
65
|
+
defaultCi: true,
|
|
66
|
+
lockCi: true,
|
|
67
|
+
blockingSeverities: ["critical", "high", "medium"],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const entryPointFiles = [
|
|
72
|
+
".cursorrules",
|
|
73
|
+
".windsurfrules",
|
|
74
|
+
"AGENTS.md",
|
|
75
|
+
".agent-override.md",
|
|
76
|
+
"mcp.json",
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const directoryCopies = [".agent-context", ".github", ".gemini", ".agents"];
|
|
80
|
+
|
|
81
|
+
function printUsage() {
|
|
82
|
+
console.log("Agentic-Senior-Core CLI");
|
|
83
|
+
console.log("");
|
|
84
|
+
console.log("Local runtime:");
|
|
85
|
+
console.log(" npx @fatidaprilian/agentic-senior-core init");
|
|
86
|
+
console.log(" bunx @fatidaprilian/agentic-senior-core init # optional Bun path");
|
|
87
|
+
console.log("");
|
|
88
|
+
console.log("Usage:");
|
|
89
|
+
console.log(" agentic-senior-core init [target-directory] [--profile <beginner|balanced|strict>] [--profile-pack <name>] [--stack <name>] [--blueprint <name>] [--ci <true|false>] [--newbie]");
|
|
90
|
+
console.log(" agentic-senior-core upgrade [target-directory] [--dry-run] [--yes]");
|
|
91
|
+
console.log(" agentic-senior-core --version");
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log("Options:");
|
|
94
|
+
console.log(" --help Show help");
|
|
95
|
+
console.log(" --version Show CLI version");
|
|
96
|
+
console.log(" --profile Choose beginner, balanced, or strict");
|
|
97
|
+
console.log(" --profile-pack Apply a team profile pack (startup, regulated, platform)");
|
|
98
|
+
console.log(" --newbie Alias for --profile beginner");
|
|
99
|
+
console.log(" --stack Override stack selection");
|
|
100
|
+
console.log(" --blueprint Override blueprint selection");
|
|
101
|
+
console.log(" --ci Override CI/CD guardrails (true|false)");
|
|
102
|
+
console.log(" --dry-run Preview upgrade without writing files");
|
|
103
|
+
console.log(" --yes Skip confirmation prompts for upgrade");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function pathExists(targetPath) {
|
|
107
|
+
try {
|
|
108
|
+
await fs.stat(targetPath);
|
|
109
|
+
return true;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function ensureDirectory(directoryPath) {
|
|
116
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function copyDirectory(sourceDirectoryPath, targetDirectoryPath) {
|
|
120
|
+
if (path.resolve(sourceDirectoryPath) === path.resolve(targetDirectoryPath)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await ensureDirectory(targetDirectoryPath);
|
|
125
|
+
const directoryEntries = await fs.readdir(sourceDirectoryPath, { withFileTypes: true });
|
|
126
|
+
|
|
127
|
+
for (const directoryEntry of directoryEntries) {
|
|
128
|
+
const sourceEntryPath = path.join(sourceDirectoryPath, directoryEntry.name);
|
|
129
|
+
const targetEntryPath = path.join(targetDirectoryPath, directoryEntry.name);
|
|
130
|
+
|
|
131
|
+
if (directoryEntry.isDirectory()) {
|
|
132
|
+
await copyDirectory(sourceEntryPath, targetEntryPath);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (path.resolve(sourceEntryPath) === path.resolve(targetEntryPath)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await fs.copyFile(sourceEntryPath, targetEntryPath);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function copyGovernanceAssetsToTarget(resolvedTargetDirectoryPath) {
|
|
145
|
+
for (const sourceDirectoryName of directoryCopies) {
|
|
146
|
+
const sourceDirectoryPath = path.join(REPO_ROOT, sourceDirectoryName);
|
|
147
|
+
if (!(await pathExists(sourceDirectoryPath))) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await copyDirectory(sourceDirectoryPath, path.join(resolvedTargetDirectoryPath, sourceDirectoryName));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const entryPointFileName of entryPointFiles) {
|
|
155
|
+
const sourceFilePath = path.join(REPO_ROOT, entryPointFileName);
|
|
156
|
+
const targetFilePath = path.join(resolvedTargetDirectoryPath, entryPointFileName);
|
|
157
|
+
|
|
158
|
+
if (!(await pathExists(sourceFilePath))) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (path.resolve(sourceFilePath) === path.resolve(targetFilePath)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await ensureDirectory(path.dirname(targetFilePath));
|
|
167
|
+
await fs.copyFile(sourceFilePath, targetFilePath);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function askChoice(promptMessage, options, userInterface) {
|
|
172
|
+
console.log(`\n${promptMessage}`);
|
|
173
|
+
options.forEach((choiceLabel, choiceIndex) => {
|
|
174
|
+
console.log(` ${choiceIndex + 1}. ${choiceLabel}`);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
while (true) {
|
|
178
|
+
const selectedRawInput = await userInterface.question("Choose a number: ");
|
|
179
|
+
const selectedIndex = Number.parseInt(selectedRawInput.trim(), 10) - 1;
|
|
180
|
+
|
|
181
|
+
if (Number.isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex >= options.length) {
|
|
182
|
+
console.log("Invalid choice. Please select a valid number.");
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return options[selectedIndex];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function askYesNo(promptMessage, userInterface, defaultValue) {
|
|
191
|
+
const suffix = typeof defaultValue === "boolean"
|
|
192
|
+
? defaultValue ? " (Y/n): " : " (y/N): "
|
|
193
|
+
: " (y/n): ";
|
|
194
|
+
|
|
195
|
+
while (true) {
|
|
196
|
+
const answer = await userInterface.question(`\n${promptMessage}${suffix}`);
|
|
197
|
+
const normalizedAnswer = answer.trim().toLowerCase();
|
|
198
|
+
|
|
199
|
+
if (!normalizedAnswer && typeof defaultValue === "boolean") {
|
|
200
|
+
return defaultValue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (normalizedAnswer === "y" || normalizedAnswer === "yes") return true;
|
|
204
|
+
if (normalizedAnswer === "n" || normalizedAnswer === "no") return false;
|
|
205
|
+
|
|
206
|
+
console.log("Please answer with 'y' or 'n'.");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function toTitleCase(fileName) {
|
|
211
|
+
return fileName
|
|
212
|
+
.replace(/\.md$/i, "")
|
|
213
|
+
.split(/[-_]/g)
|
|
214
|
+
.map((wordPart) => wordPart.charAt(0).toUpperCase() + wordPart.slice(1))
|
|
215
|
+
.join(" ");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function normalizeChoiceInput(rawInput) {
|
|
219
|
+
return rawInput.trim().toLowerCase().replace(/\s+/g, "-");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function matchFileNameFromInput(rawInput, fileNames) {
|
|
223
|
+
const normalizedInput = normalizeChoiceInput(rawInput);
|
|
224
|
+
|
|
225
|
+
return fileNames.find((fileName) => {
|
|
226
|
+
const normalizedFileName = normalizeChoiceInput(fileName.replace(/\.md$/i, ""));
|
|
227
|
+
const normalizedTitle = normalizeChoiceInput(toTitleCase(fileName));
|
|
228
|
+
return normalizedInput === normalizedFileName || normalizedInput === normalizedTitle;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function matchProfileNameFromInput(rawInput) {
|
|
233
|
+
const normalizedInput = normalizeChoiceInput(rawInput);
|
|
234
|
+
return Object.keys(PROFILE_PRESETS).find((profileName) => profileName === normalizedInput) || null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function parseBooleanSetting(rawBooleanValue, contextLabel) {
|
|
238
|
+
const normalizedValue = normalizeChoiceInput(rawBooleanValue);
|
|
239
|
+
|
|
240
|
+
if (normalizedValue === "true") {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (normalizedValue === "false") {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
throw new Error(`Invalid boolean value for ${contextLabel}: ${rawBooleanValue}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function parseBlockingSeverities(rawSeverityValues, fileName) {
|
|
252
|
+
const parsedSeverities = rawSeverityValues
|
|
253
|
+
.split(",")
|
|
254
|
+
.map((severityValue) => normalizeChoiceInput(severityValue))
|
|
255
|
+
.filter(Boolean);
|
|
256
|
+
|
|
257
|
+
if (parsedSeverities.length === 0) {
|
|
258
|
+
throw new Error(`Profile pack ${fileName} must define at least one blocking severity.`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const invalidSeverity = parsedSeverities.find((severityValue) => !ALLOWED_SEVERITY_LEVELS.has(severityValue));
|
|
262
|
+
if (invalidSeverity) {
|
|
263
|
+
throw new Error(`Profile pack ${fileName} uses unsupported severity: ${invalidSeverity}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return parsedSeverities;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function parseProfilePackContent(fileName, profilePackContent) {
|
|
270
|
+
const parsedFields = {};
|
|
271
|
+
const profilePackLines = profilePackContent.split(/\r?\n/);
|
|
272
|
+
|
|
273
|
+
for (const profilePackLine of profilePackLines) {
|
|
274
|
+
const lineMatch = profilePackLine.match(/^([A-Za-z][A-Za-z0-9 ]+):\s*(.+)$/);
|
|
275
|
+
if (!lineMatch) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const fieldName = lineMatch[1].trim();
|
|
280
|
+
const fieldValue = lineMatch[2].trim();
|
|
281
|
+
parsedFields[fieldName] = fieldValue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (const requiredFieldName of PROFILE_PACK_REQUIRED_FIELDS) {
|
|
285
|
+
if (!parsedFields[requiredFieldName]) {
|
|
286
|
+
throw new Error(`Profile pack ${fileName} is missing required field: ${requiredFieldName}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const defaultProfileName = matchProfileNameFromInput(parsedFields.defaultProfile);
|
|
291
|
+
if (!defaultProfileName) {
|
|
292
|
+
throw new Error(`Profile pack ${fileName} has invalid defaultProfile: ${parsedFields.defaultProfile}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
fileName,
|
|
297
|
+
slug: normalizeChoiceInput(parsedFields.slug),
|
|
298
|
+
displayName: parsedFields.displayName,
|
|
299
|
+
description: parsedFields.description,
|
|
300
|
+
defaultProfileName,
|
|
301
|
+
defaultStackFileName: parsedFields.defaultStack.trim(),
|
|
302
|
+
defaultBlueprintFileName: parsedFields.defaultBlueprint.trim(),
|
|
303
|
+
defaultCi: parseBooleanSetting(parsedFields.ciGuardrails, `${fileName} ciGuardrails`),
|
|
304
|
+
lockCi: parseBooleanSetting(parsedFields.lockCi, `${fileName} lockCi`),
|
|
305
|
+
blockingSeverities: parseBlockingSeverities(parsedFields.blockingSeverities, fileName),
|
|
306
|
+
owner: parsedFields.owner || null,
|
|
307
|
+
lastUpdated: parsedFields.lastUpdated || null,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function collectProfilePacks(targetDirectoryPath) {
|
|
312
|
+
const profilePackDirectoryPath = path.join(targetDirectoryPath, ".agent-context", PROFILE_PACKS_DIRECTORY_NAME);
|
|
313
|
+
if (!(await pathExists(profilePackDirectoryPath))) {
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const profilePackFileNames = await collectFileNames(profilePackDirectoryPath);
|
|
318
|
+
const profilePackDefinitions = [];
|
|
319
|
+
|
|
320
|
+
for (const profilePackFileName of profilePackFileNames) {
|
|
321
|
+
const profilePackFilePath = path.join(profilePackDirectoryPath, profilePackFileName);
|
|
322
|
+
const profilePackContent = await fs.readFile(profilePackFilePath, "utf8");
|
|
323
|
+
profilePackDefinitions.push(parseProfilePackContent(profilePackFileName, profilePackContent));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return profilePackDefinitions;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function findProfilePackByInput(profilePackInput, profilePackDefinitions) {
|
|
330
|
+
const normalizedProfilePackInput = normalizeChoiceInput(profilePackInput);
|
|
331
|
+
|
|
332
|
+
return profilePackDefinitions.find((profilePackDefinition) => {
|
|
333
|
+
const normalizedFileName = normalizeChoiceInput(profilePackDefinition.fileName.replace(/\.md$/i, ""));
|
|
334
|
+
const normalizedSlug = normalizeChoiceInput(profilePackDefinition.slug);
|
|
335
|
+
const normalizedDisplayName = normalizeChoiceInput(profilePackDefinition.displayName);
|
|
336
|
+
|
|
337
|
+
return normalizedProfilePackInput === normalizedFileName
|
|
338
|
+
|| normalizedProfilePackInput === normalizedSlug
|
|
339
|
+
|| normalizedProfilePackInput === normalizedDisplayName;
|
|
340
|
+
}) || null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function collectFileNames(folderPath) {
|
|
344
|
+
const fileNames = await fs.readdir(folderPath, { withFileTypes: true });
|
|
345
|
+
return fileNames
|
|
346
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
|
|
347
|
+
.map((entry) => entry.name)
|
|
348
|
+
.sort((leftName, rightName) => leftName.localeCompare(rightName));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function collectProjectMarkers(targetDirectoryPath) {
|
|
352
|
+
const markerNames = new Set();
|
|
353
|
+
const directoryEntries = await fs.readdir(targetDirectoryPath, { withFileTypes: true });
|
|
354
|
+
|
|
355
|
+
for (const directoryEntry of directoryEntries) {
|
|
356
|
+
if (directoryEntry.name === ".git" || directoryEntry.name === "node_modules") {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
markerNames.add(directoryEntry.name);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return markerNames;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function detectProjectContext(targetDirectoryPath) {
|
|
367
|
+
const markerNames = await collectProjectMarkers(targetDirectoryPath);
|
|
368
|
+
const detectionCandidates = [];
|
|
369
|
+
const hasExistingProjectFiles = markerNames.size > 0;
|
|
370
|
+
|
|
371
|
+
if (markerNames.has("package.json") || markerNames.has("tsconfig.json") || markerNames.has("next.config.js") || markerNames.has("next.config.mjs")) {
|
|
372
|
+
const evidence = [];
|
|
373
|
+
let confidenceScore = 0.7;
|
|
374
|
+
|
|
375
|
+
if (markerNames.has("package.json")) {
|
|
376
|
+
evidence.push("package.json");
|
|
377
|
+
confidenceScore += 0.12;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (markerNames.has("tsconfig.json")) {
|
|
381
|
+
evidence.push("tsconfig.json");
|
|
382
|
+
confidenceScore += 0.12;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (markerNames.has("next.config.js") || markerNames.has("next.config.mjs")) {
|
|
386
|
+
evidence.push("Next.js config");
|
|
387
|
+
confidenceScore += 0.05;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
detectionCandidates.push({
|
|
391
|
+
stackFileName: "typescript.md",
|
|
392
|
+
confidenceScore: Math.min(confidenceScore, 0.97),
|
|
393
|
+
evidence,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (markerNames.has("pyproject.toml") || markerNames.has("requirements.txt")) {
|
|
398
|
+
detectionCandidates.push({
|
|
399
|
+
stackFileName: "python.md",
|
|
400
|
+
confidenceScore: markerNames.has("pyproject.toml") ? 0.96 : 0.78,
|
|
401
|
+
evidence: markerNames.has("pyproject.toml") ? ["pyproject.toml"] : ["requirements.txt"],
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (markerNames.has("pom.xml") || markerNames.has("build.gradle") || markerNames.has("build.gradle.kts")) {
|
|
406
|
+
const evidence = [];
|
|
407
|
+
if (markerNames.has("pom.xml")) evidence.push("pom.xml");
|
|
408
|
+
if (markerNames.has("build.gradle") || markerNames.has("build.gradle.kts")) evidence.push("Gradle build file");
|
|
409
|
+
detectionCandidates.push({
|
|
410
|
+
stackFileName: "java.md",
|
|
411
|
+
confidenceScore: markerNames.has("pom.xml") ? 0.95 : 0.84,
|
|
412
|
+
evidence,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (markerNames.has("composer.json")) {
|
|
417
|
+
detectionCandidates.push({
|
|
418
|
+
stackFileName: "php.md",
|
|
419
|
+
confidenceScore: 0.95,
|
|
420
|
+
evidence: ["composer.json"],
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (markerNames.has("go.mod")) {
|
|
425
|
+
detectionCandidates.push({
|
|
426
|
+
stackFileName: "go.md",
|
|
427
|
+
confidenceScore: 0.96,
|
|
428
|
+
evidence: ["go.mod"],
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (markerNames.has("Cargo.toml")) {
|
|
433
|
+
detectionCandidates.push({
|
|
434
|
+
stackFileName: "rust.md",
|
|
435
|
+
confidenceScore: 0.96,
|
|
436
|
+
evidence: ["Cargo.toml"],
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (markerNames.has("Gemfile")) {
|
|
441
|
+
detectionCandidates.push({
|
|
442
|
+
stackFileName: "ruby.md",
|
|
443
|
+
confidenceScore: 0.95,
|
|
444
|
+
evidence: ["Gemfile"],
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const hasDotNetMarker = Array.from(markerNames).some((markerName) => markerName.endsWith(".sln") || markerName.endsWith(".csproj"));
|
|
449
|
+
if (hasDotNetMarker) {
|
|
450
|
+
detectionCandidates.push({
|
|
451
|
+
stackFileName: "csharp.md",
|
|
452
|
+
confidenceScore: 0.95,
|
|
453
|
+
evidence: [".sln or .csproj file"],
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (detectionCandidates.length === 0) {
|
|
458
|
+
return {
|
|
459
|
+
hasExistingProjectFiles,
|
|
460
|
+
recommendedStackFileName: null,
|
|
461
|
+
recommendedBlueprintFileName: null,
|
|
462
|
+
confidenceLabel: null,
|
|
463
|
+
confidenceScore: 0,
|
|
464
|
+
confidenceGap: 0,
|
|
465
|
+
detectionReasoning: "No known project markers were detected.",
|
|
466
|
+
rankedCandidates: [],
|
|
467
|
+
evidence: [],
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
detectionCandidates.sort((leftCandidate, rightCandidate) => rightCandidate.confidenceScore - leftCandidate.confidenceScore);
|
|
472
|
+
const strongestCandidate = detectionCandidates[0];
|
|
473
|
+
const secondStrongestCandidate = detectionCandidates[1];
|
|
474
|
+
const confidenceGap = secondStrongestCandidate
|
|
475
|
+
? Number((strongestCandidate.confidenceScore - secondStrongestCandidate.confidenceScore).toFixed(2))
|
|
476
|
+
: Number(strongestCandidate.confidenceScore.toFixed(2));
|
|
477
|
+
const isAmbiguous = secondStrongestCandidate
|
|
478
|
+
&& confidenceGap < 0.08;
|
|
479
|
+
const confidenceLabel = strongestCandidate.confidenceScore >= 0.9
|
|
480
|
+
? "high"
|
|
481
|
+
: strongestCandidate.confidenceScore >= 0.78
|
|
482
|
+
? "medium"
|
|
483
|
+
: "low";
|
|
484
|
+
const evidence = isAmbiguous
|
|
485
|
+
? [...strongestCandidate.evidence, `multiple stack signals detected`]
|
|
486
|
+
: strongestCandidate.evidence;
|
|
487
|
+
const rankedCandidates = detectionCandidates.slice(0, 3).map((detectionCandidate) => ({
|
|
488
|
+
stackFileName: detectionCandidate.stackFileName,
|
|
489
|
+
confidenceScore: Number(detectionCandidate.confidenceScore.toFixed(2)),
|
|
490
|
+
evidence: detectionCandidate.evidence,
|
|
491
|
+
}));
|
|
492
|
+
const detectionReasoning = isAmbiguous
|
|
493
|
+
? `Top signal ${toTitleCase(strongestCandidate.stackFileName)} is close to ${toTitleCase(secondStrongestCandidate.stackFileName)} (confidence gap ${confidenceGap}).`
|
|
494
|
+
: `Top signal ${toTitleCase(strongestCandidate.stackFileName)} won with confidence ${strongestCandidate.confidenceScore.toFixed(2)} from markers: ${strongestCandidate.evidence.join(", ") || "none"}.`;
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
hasExistingProjectFiles,
|
|
498
|
+
recommendedStackFileName: strongestCandidate.stackFileName,
|
|
499
|
+
recommendedBlueprintFileName: BLUEPRINT_RECOMMENDATIONS[strongestCandidate.stackFileName] || null,
|
|
500
|
+
confidenceLabel,
|
|
501
|
+
confidenceScore: strongestCandidate.confidenceScore,
|
|
502
|
+
confidenceGap,
|
|
503
|
+
detectionReasoning,
|
|
504
|
+
rankedCandidates,
|
|
505
|
+
evidence,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function formatBlockingSeverities(blockingSeverities) {
|
|
510
|
+
return blockingSeverities.join(", ");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function formatDuration(durationMs) {
|
|
514
|
+
const durationInSeconds = (durationMs / 1000).toFixed(1);
|
|
515
|
+
return `${durationInSeconds}s`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function buildDetectionSummary(projectDetection) {
|
|
519
|
+
if (!projectDetection.recommendedStackFileName) {
|
|
520
|
+
return "I did not find enough stack markers to auto-detect this project confidently.";
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const readableEvidence = projectDetection.evidence.length > 0
|
|
524
|
+
? projectDetection.evidence.join(", ")
|
|
525
|
+
: "basic project markers";
|
|
526
|
+
|
|
527
|
+
const confidenceGapSummary = typeof projectDetection.confidenceGap === "number"
|
|
528
|
+
? ` Confidence gap: ${projectDetection.confidenceGap}.`
|
|
529
|
+
: "";
|
|
530
|
+
|
|
531
|
+
return `This folder looks like ${toTitleCase(projectDetection.recommendedStackFileName)} with ${projectDetection.confidenceLabel} confidence based on ${readableEvidence}.${confidenceGapSummary}`;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function formatDetectionCandidates(rankedCandidates) {
|
|
535
|
+
if (!rankedCandidates?.length) {
|
|
536
|
+
return "No ranked candidates available.";
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return rankedCandidates
|
|
540
|
+
.map((candidate, candidateIndex) => {
|
|
541
|
+
const evidenceSummary = candidate.evidence?.length ? candidate.evidence.join(", ") : "no direct markers";
|
|
542
|
+
return `${candidateIndex + 1}. ${toTitleCase(candidate.stackFileName)} (score ${candidate.confidenceScore}) via ${evidenceSummary}`;
|
|
543
|
+
})
|
|
544
|
+
.join("\n");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function writeSelectedPolicy(targetDirectoryPath, selectedProfileName) {
|
|
548
|
+
const policyFilePath = path.join(targetDirectoryPath, ".agent-context", "policies", POLICY_FILE_NAME);
|
|
549
|
+
const parsedPolicy = JSON.parse(await fs.readFile(policyFilePath, "utf8"));
|
|
550
|
+
parsedPolicy.selectedProfile = selectedProfileName;
|
|
551
|
+
await fs.writeFile(policyFilePath, JSON.stringify(parsedPolicy, null, 2) + "\n", "utf8");
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function writeOnboardingReport({
|
|
555
|
+
targetDirectoryPath,
|
|
556
|
+
selectedProfileName,
|
|
557
|
+
selectedProfilePack,
|
|
558
|
+
selectedStackFileName,
|
|
559
|
+
selectedBlueprintFileName,
|
|
560
|
+
includeCiGuardrails,
|
|
561
|
+
setupDurationMs,
|
|
562
|
+
projectDetection,
|
|
563
|
+
operationMode = "init",
|
|
564
|
+
}) {
|
|
565
|
+
const onboardingReportPath = path.join(targetDirectoryPath, ".agent-context", "state", "onboarding-report.json");
|
|
566
|
+
const onboardingReport = {
|
|
567
|
+
cliVersion: CLI_VERSION,
|
|
568
|
+
generatedAt: new Date().toISOString(),
|
|
569
|
+
operationMode,
|
|
570
|
+
selectedProfile: selectedProfileName,
|
|
571
|
+
selectedProfilePack: selectedProfilePack
|
|
572
|
+
? {
|
|
573
|
+
name: selectedProfilePack.slug,
|
|
574
|
+
sourceFile: selectedProfilePack.fileName,
|
|
575
|
+
}
|
|
576
|
+
: null,
|
|
577
|
+
selectedStack: selectedStackFileName,
|
|
578
|
+
selectedBlueprint: selectedBlueprintFileName,
|
|
579
|
+
ciGuardrailsEnabled: includeCiGuardrails,
|
|
580
|
+
setupDurationMs,
|
|
581
|
+
autoDetection: {
|
|
582
|
+
recommendedStack: projectDetection.recommendedStackFileName,
|
|
583
|
+
recommendedBlueprint: projectDetection.recommendedBlueprintFileName,
|
|
584
|
+
confidenceLabel: projectDetection.confidenceLabel,
|
|
585
|
+
confidenceScore: projectDetection.confidenceScore,
|
|
586
|
+
confidenceGap: projectDetection.confidenceGap,
|
|
587
|
+
detectionReasoning: projectDetection.detectionReasoning,
|
|
588
|
+
rankedCandidates: projectDetection.rankedCandidates,
|
|
589
|
+
evidence: projectDetection.evidence,
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
await fs.writeFile(onboardingReportPath, JSON.stringify(onboardingReport, null, 2) + "\n", "utf8");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
async function loadOnboardingReportIfExists(targetDirectoryPath) {
|
|
597
|
+
const onboardingReportPath = path.join(targetDirectoryPath, ".agent-context", "state", "onboarding-report.json");
|
|
598
|
+
if (!(await pathExists(onboardingReportPath))) {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const onboardingReportContent = await fs.readFile(onboardingReportPath, "utf8");
|
|
603
|
+
return JSON.parse(onboardingReportContent);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function buildCompiledRulesContent({
|
|
607
|
+
targetDirectoryPath,
|
|
608
|
+
selectedProfileName,
|
|
609
|
+
selectedStackFileName,
|
|
610
|
+
selectedBlueprintFileName,
|
|
611
|
+
includeCiGuardrails,
|
|
612
|
+
}) {
|
|
613
|
+
const resolvedTargetDirectoryPath = path.resolve(targetDirectoryPath);
|
|
614
|
+
const selectedRulesDirectoryPath = path.join(resolvedTargetDirectoryPath, ".agent-context", "rules");
|
|
615
|
+
const selectedStacksDirectoryPath = path.join(resolvedTargetDirectoryPath, ".agent-context", "stacks");
|
|
616
|
+
const selectedBlueprintsDirectoryPath = path.join(resolvedTargetDirectoryPath, ".agent-context", "blueprints");
|
|
617
|
+
const selectedStateDirectoryPath = path.join(resolvedTargetDirectoryPath, ".agent-context", "state");
|
|
618
|
+
const selectedReviewDirectoryPath = path.join(resolvedTargetDirectoryPath, ".agent-context", "review-checklists");
|
|
619
|
+
|
|
620
|
+
const universalRuleFileNames = await collectFileNames(selectedRulesDirectoryPath);
|
|
621
|
+
const contextBlocks = [];
|
|
622
|
+
|
|
623
|
+
for (const universalRuleFileName of universalRuleFileNames) {
|
|
624
|
+
const universalRuleFilePath = path.join(selectedRulesDirectoryPath, universalRuleFileName);
|
|
625
|
+
const universalRuleContent = await fs.readFile(universalRuleFilePath, "utf8");
|
|
626
|
+
|
|
627
|
+
contextBlocks.push(
|
|
628
|
+
`## UNIVERSAL RULE: ${universalRuleFileName}\nSource: .agent-context/rules/${universalRuleFileName}\n\n${universalRuleContent.trim()}`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const stackFilePath = path.join(selectedStacksDirectoryPath, selectedStackFileName);
|
|
633
|
+
const stackContent = await fs.readFile(stackFilePath, "utf8");
|
|
634
|
+
contextBlocks.push(
|
|
635
|
+
`## STACK PROFILE: ${selectedStackFileName}\nSource: .agent-context/stacks/${selectedStackFileName}\n\n${stackContent.trim()}`
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
const blueprintFilePath = path.join(selectedBlueprintsDirectoryPath, selectedBlueprintFileName);
|
|
639
|
+
const blueprintContent = await fs.readFile(blueprintFilePath, "utf8");
|
|
640
|
+
contextBlocks.push(
|
|
641
|
+
`## BLUEPRINT PROFILE: ${selectedBlueprintFileName}\nSource: .agent-context/blueprints/${selectedBlueprintFileName}\n\n${blueprintContent.trim()}`
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
if (includeCiGuardrails) {
|
|
645
|
+
const githubCiBlueprintContent = await fs.readFile(path.join(selectedBlueprintsDirectoryPath, "ci-github-actions.md"), "utf8");
|
|
646
|
+
const gitlabCiBlueprintContent = await fs.readFile(path.join(selectedBlueprintsDirectoryPath, "ci-gitlab.md"), "utf8");
|
|
647
|
+
|
|
648
|
+
contextBlocks.push(
|
|
649
|
+
`## CI/CD GUARDRAILS: ci-github-actions.md\nSource: .agent-context/blueprints/ci-github-actions.md\n\n${githubCiBlueprintContent.trim()}`
|
|
650
|
+
);
|
|
651
|
+
contextBlocks.push(
|
|
652
|
+
`## CI/CD GUARDRAILS: ci-gitlab.md\nSource: .agent-context/blueprints/ci-gitlab.md\n\n${gitlabCiBlueprintContent.trim()}`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const architectureMapContent = await fs.readFile(path.join(selectedStateDirectoryPath, "architecture-map.md"), "utf8");
|
|
657
|
+
const dependencyMapContent = await fs.readFile(path.join(selectedStateDirectoryPath, "dependency-map.md"), "utf8");
|
|
658
|
+
const prChecklistContent = await fs.readFile(path.join(selectedReviewDirectoryPath, "pr-checklist.md"), "utf8");
|
|
659
|
+
|
|
660
|
+
contextBlocks.push(
|
|
661
|
+
`## STATE MAP: architecture-map.md\nSource: .agent-context/state/architecture-map.md\n\n${architectureMapContent.trim()}`
|
|
662
|
+
);
|
|
663
|
+
contextBlocks.push(
|
|
664
|
+
`## STATE MAP: dependency-map.md\nSource: .agent-context/state/dependency-map.md\n\n${dependencyMapContent.trim()}`
|
|
665
|
+
);
|
|
666
|
+
contextBlocks.push(
|
|
667
|
+
`## REVIEW CHECKLIST: pr-checklist.md\nSource: .agent-context/review-checklists/pr-checklist.md\n\n${prChecklistContent.trim()}`
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
return [
|
|
671
|
+
"# AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET",
|
|
672
|
+
"",
|
|
673
|
+
`Generated by Agentic-Senior-Core CLI v${CLI_VERSION}`,
|
|
674
|
+
`Timestamp: ${new Date().toISOString()}`,
|
|
675
|
+
`Selected profile: ${selectedProfileName}`,
|
|
676
|
+
`Selected policy file: .agent-context/policies/${POLICY_FILE_NAME}`,
|
|
677
|
+
"",
|
|
678
|
+
"## GOVERNANCE PRECEDENCE",
|
|
679
|
+
"1. Follow this compiled rulebook as the primary source.",
|
|
680
|
+
"2. Resolve exceptions from .agent-override.md only when explicitly defined.",
|
|
681
|
+
"3. Use architecture-map.md and dependency-map.md as change safety boundaries.",
|
|
682
|
+
"4. Enforce pr-checklist.md before declaring completion.",
|
|
683
|
+
"",
|
|
684
|
+
"## OVERRIDE PROTOCOL",
|
|
685
|
+
"- Default: strict compliance with this file.",
|
|
686
|
+
"- Exception path: .agent-override.md may explicitly allow narrow deviations.",
|
|
687
|
+
"- Scope policy: every override must include module scope, rationale, and expiry date.",
|
|
688
|
+
"",
|
|
689
|
+
...contextBlocks,
|
|
690
|
+
"",
|
|
691
|
+
].join("\n");
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
async function compileDynamicContext({
|
|
695
|
+
targetDirectoryPath,
|
|
696
|
+
selectedProfileName,
|
|
697
|
+
selectedStackFileName,
|
|
698
|
+
selectedBlueprintFileName,
|
|
699
|
+
includeCiGuardrails,
|
|
700
|
+
}) {
|
|
701
|
+
const resolvedTargetDirectoryPath = path.resolve(targetDirectoryPath);
|
|
702
|
+
const compiledRules = await buildCompiledRulesContent({
|
|
703
|
+
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
704
|
+
selectedProfileName,
|
|
705
|
+
selectedStackFileName,
|
|
706
|
+
selectedBlueprintFileName,
|
|
707
|
+
includeCiGuardrails,
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
await fs.writeFile(path.join(resolvedTargetDirectoryPath, ".cursorrules"), compiledRules, "utf8");
|
|
711
|
+
await fs.writeFile(path.join(resolvedTargetDirectoryPath, ".windsurfrules"), compiledRules, "utf8");
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async function runInitCommand(targetDirectoryArgument, initOptions = {}) {
|
|
715
|
+
const resolvedTargetDirectoryPath = path.resolve(targetDirectoryArgument || ".");
|
|
716
|
+
const setupStartedAt = Date.now();
|
|
717
|
+
await ensureDirectory(resolvedTargetDirectoryPath);
|
|
718
|
+
|
|
719
|
+
const userInterface = readline.createInterface({ input, output });
|
|
720
|
+
|
|
721
|
+
try {
|
|
722
|
+
const stackFileNames = await collectFileNames(path.join(AGENT_CONTEXT_DIR, "stacks"));
|
|
723
|
+
const blueprintFileNames = await collectFileNames(path.join(AGENT_CONTEXT_DIR, "blueprints"));
|
|
724
|
+
const profilePackDefinitions = await collectProfilePacks(REPO_ROOT);
|
|
725
|
+
|
|
726
|
+
const selectedStackFileNameFromOption = initOptions.stack
|
|
727
|
+
? matchFileNameFromInput(initOptions.stack, stackFileNames)
|
|
728
|
+
: null;
|
|
729
|
+
const selectedBlueprintFileNameFromOption = initOptions.blueprint
|
|
730
|
+
? matchFileNameFromInput(initOptions.blueprint, blueprintFileNames)
|
|
731
|
+
: null;
|
|
732
|
+
const selectedProfilePack = initOptions.profilePack
|
|
733
|
+
? findProfilePackByInput(initOptions.profilePack, profilePackDefinitions)
|
|
734
|
+
: null;
|
|
735
|
+
|
|
736
|
+
if (initOptions.stack && !selectedStackFileNameFromOption) {
|
|
737
|
+
throw new Error(`Unknown stack: ${initOptions.stack}`);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (initOptions.blueprint && !selectedBlueprintFileNameFromOption) {
|
|
741
|
+
throw new Error(`Unknown blueprint: ${initOptions.blueprint}`);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (initOptions.profilePack && !selectedProfilePack) {
|
|
745
|
+
throw new Error(`Unknown profile pack: ${initOptions.profilePack}`);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (selectedProfilePack && !stackFileNames.includes(selectedProfilePack.defaultStackFileName)) {
|
|
749
|
+
throw new Error(
|
|
750
|
+
`Profile pack ${selectedProfilePack.fileName} references unknown stack file: ${selectedProfilePack.defaultStackFileName}`
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (selectedProfilePack && !blueprintFileNames.includes(selectedProfilePack.defaultBlueprintFileName)) {
|
|
755
|
+
throw new Error(
|
|
756
|
+
`Profile pack ${selectedProfilePack.fileName} references unknown blueprint file: ${selectedProfilePack.defaultBlueprintFileName}`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
console.log(`\nAgentic-Senior-Core CLI v${CLI_VERSION}`);
|
|
761
|
+
console.log("I will copy governance files into your target folder and compile a single rulebook for your AI tools.");
|
|
762
|
+
|
|
763
|
+
const projectDetection = await detectProjectContext(resolvedTargetDirectoryPath);
|
|
764
|
+
if (projectDetection.hasExistingProjectFiles) {
|
|
765
|
+
console.log("I found files in the target directory, so I checked whether this already looks like an existing project.");
|
|
766
|
+
console.log(buildDetectionSummary(projectDetection));
|
|
767
|
+
console.log("Detection reasoning:");
|
|
768
|
+
console.log(projectDetection.detectionReasoning);
|
|
769
|
+
console.log("Top candidates:");
|
|
770
|
+
console.log(formatDetectionCandidates(projectDetection.rankedCandidates));
|
|
771
|
+
} else {
|
|
772
|
+
console.log("The target directory is empty, so I will guide you through a fresh setup.");
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const selectedProfileName = initOptions.profile
|
|
776
|
+
? initOptions.profile
|
|
777
|
+
: initOptions.newbie
|
|
778
|
+
? "beginner"
|
|
779
|
+
: selectedProfilePack?.defaultProfileName
|
|
780
|
+
? selectedProfilePack.defaultProfileName
|
|
781
|
+
: normalizeChoiceInput(await askChoice(
|
|
782
|
+
"How much guidance do you want?",
|
|
783
|
+
Object.values(PROFILE_PRESETS).map((profilePreset) => `${profilePreset.displayName} — ${profilePreset.description}`),
|
|
784
|
+
userInterface
|
|
785
|
+
)).split("-")[0];
|
|
786
|
+
|
|
787
|
+
const selectedProfile = PROFILE_PRESETS[selectedProfileName];
|
|
788
|
+
if (!selectedProfile) {
|
|
789
|
+
throw new Error(`Unknown profile: ${selectedProfileName}`);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
console.log(`\nSelected profile: ${selectedProfile.displayName}`);
|
|
793
|
+
console.log(`This profile will block these review severities in CI: ${formatBlockingSeverities(selectedProfile.blockingSeverities)}.`);
|
|
794
|
+
|
|
795
|
+
if (selectedProfilePack) {
|
|
796
|
+
console.log(`Applying team profile pack: ${selectedProfilePack.displayName}.`);
|
|
797
|
+
console.log(`Pack defaults: stack ${toTitleCase(selectedProfilePack.defaultStackFileName)}, blueprint ${toTitleCase(selectedProfilePack.defaultBlueprintFileName)}.`);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const shouldApplyDetectedStack = projectDetection.recommendedStackFileName && !selectedStackFileNameFromOption
|
|
801
|
+
? await askYesNo(
|
|
802
|
+
`Use the detected stack recommendation (${toTitleCase(projectDetection.recommendedStackFileName)})?`,
|
|
803
|
+
userInterface,
|
|
804
|
+
projectDetection.confidenceLabel === "high"
|
|
805
|
+
)
|
|
806
|
+
: false;
|
|
807
|
+
|
|
808
|
+
const stackDisplayChoices = stackFileNames.map((stackFileName) => toTitleCase(stackFileName));
|
|
809
|
+
const blueprintDisplayChoices = blueprintFileNames.map((blueprintFileName) => toTitleCase(blueprintFileName));
|
|
810
|
+
|
|
811
|
+
const selectedResolvedStackFileName = selectedStackFileNameFromOption
|
|
812
|
+
|| (shouldApplyDetectedStack ? projectDetection.recommendedStackFileName : null)
|
|
813
|
+
|| selectedProfilePack?.defaultStackFileName
|
|
814
|
+
|| selectedProfile.defaultStackFileName
|
|
815
|
+
|| stackFileNames[
|
|
816
|
+
stackDisplayChoices.indexOf(
|
|
817
|
+
await askChoice("Which stack should this governance pack target?", stackDisplayChoices, userInterface)
|
|
818
|
+
)
|
|
819
|
+
];
|
|
820
|
+
|
|
821
|
+
const recommendedBlueprintFileName = shouldApplyDetectedStack
|
|
822
|
+
? projectDetection.recommendedBlueprintFileName
|
|
823
|
+
: BLUEPRINT_RECOMMENDATIONS[selectedResolvedStackFileName] || null;
|
|
824
|
+
|
|
825
|
+
if (!recommendedBlueprintFileName && !selectedBlueprintFileNameFromOption && !selectedProfile.defaultBlueprintFileName) {
|
|
826
|
+
console.log("\nI could not map that stack to a first-party blueprint automatically, so I will ask you to choose one.");
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const selectedResolvedBlueprintFileName = selectedBlueprintFileNameFromOption
|
|
830
|
+
|| recommendedBlueprintFileName
|
|
831
|
+
|| selectedProfilePack?.defaultBlueprintFileName
|
|
832
|
+
|| selectedProfile.defaultBlueprintFileName
|
|
833
|
+
|| blueprintFileNames[
|
|
834
|
+
blueprintDisplayChoices.indexOf(
|
|
835
|
+
await askChoice("Which blueprint should I scaffold into the compiled rulebook?", blueprintDisplayChoices, userInterface)
|
|
836
|
+
)
|
|
837
|
+
];
|
|
838
|
+
|
|
839
|
+
const includeCiGuardrails = typeof initOptions.ci === "boolean"
|
|
840
|
+
? initOptions.ci
|
|
841
|
+
: selectedProfilePack?.lockCi
|
|
842
|
+
? selectedProfilePack.defaultCi
|
|
843
|
+
: typeof selectedProfilePack?.defaultCi === "boolean"
|
|
844
|
+
? selectedProfilePack.defaultCi
|
|
845
|
+
: selectedProfile.lockCi
|
|
846
|
+
? selectedProfile.defaultCi
|
|
847
|
+
: await askYesNo("Enable CI/CD guardrails and the LLM Judge policy?", userInterface, selectedProfile.defaultCi);
|
|
848
|
+
|
|
849
|
+
await copyGovernanceAssetsToTarget(resolvedTargetDirectoryPath);
|
|
850
|
+
|
|
851
|
+
await compileDynamicContext({
|
|
852
|
+
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
853
|
+
selectedProfileName,
|
|
854
|
+
selectedProfilePack,
|
|
855
|
+
selectedStackFileName: selectedResolvedStackFileName,
|
|
856
|
+
selectedBlueprintFileName: selectedResolvedBlueprintFileName,
|
|
857
|
+
includeCiGuardrails,
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
await writeSelectedPolicy(resolvedTargetDirectoryPath, selectedProfileName);
|
|
861
|
+
|
|
862
|
+
const setupDurationMs = Date.now() - setupStartedAt;
|
|
863
|
+
await writeOnboardingReport({
|
|
864
|
+
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
865
|
+
selectedProfileName,
|
|
866
|
+
selectedProfilePack,
|
|
867
|
+
selectedStackFileName: selectedResolvedStackFileName,
|
|
868
|
+
selectedBlueprintFileName: selectedResolvedBlueprintFileName,
|
|
869
|
+
includeCiGuardrails,
|
|
870
|
+
setupDurationMs,
|
|
871
|
+
projectDetection,
|
|
872
|
+
operationMode: "init",
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
console.log("\nInitialization complete.");
|
|
876
|
+
console.log(`- Target directory: ${resolvedTargetDirectoryPath}`);
|
|
877
|
+
console.log(`- Profile: ${selectedProfile.displayName}`);
|
|
878
|
+
if (selectedProfilePack) {
|
|
879
|
+
console.log(`- Team profile pack: ${selectedProfilePack.displayName}`);
|
|
880
|
+
}
|
|
881
|
+
console.log(`- Stack: ${toTitleCase(selectedResolvedStackFileName)}`);
|
|
882
|
+
console.log(`- Blueprint: ${toTitleCase(selectedResolvedBlueprintFileName)}`);
|
|
883
|
+
console.log(`- CI/CD guardrails: ${includeCiGuardrails ? "enabled" : "disabled"}`);
|
|
884
|
+
console.log(`- Blocking severities: ${formatBlockingSeverities(selectedProfile.blockingSeverities)}`);
|
|
885
|
+
console.log(`- Setup time: ${formatDuration(setupDurationMs)}`);
|
|
886
|
+
console.log("- Generated files: .cursorrules, .windsurfrules, and .agent-context/state/onboarding-report.json");
|
|
887
|
+
console.log("\nPlain-language summary:");
|
|
888
|
+
console.log(`I prepared a ${selectedProfile.displayName.toLowerCase()} governance pack for a ${toTitleCase(selectedResolvedStackFileName)} project using the ${toTitleCase(selectedResolvedBlueprintFileName)} blueprint.`);
|
|
889
|
+
console.log("Your AI tools will now receive one compiled rulebook plus the original source rules, and your review threshold is stored in .agent-context/policies/llm-judge-threshold.json.");
|
|
890
|
+
} finally {
|
|
891
|
+
userInterface.close();
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions = {}) {
|
|
896
|
+
const resolvedTargetDirectoryPath = path.resolve(targetDirectoryArgument || ".");
|
|
897
|
+
const setupStartedAt = Date.now();
|
|
898
|
+
await ensureDirectory(resolvedTargetDirectoryPath);
|
|
899
|
+
|
|
900
|
+
const userInterface = readline.createInterface({ input, output });
|
|
901
|
+
|
|
902
|
+
try {
|
|
903
|
+
console.log(`\nAgentic-Senior-Core CLI v${CLI_VERSION}`);
|
|
904
|
+
console.log("Running upgrade assistant for an existing repository.");
|
|
905
|
+
|
|
906
|
+
await copyGovernanceAssetsToTarget(resolvedTargetDirectoryPath);
|
|
907
|
+
|
|
908
|
+
const stackFileNames = await collectFileNames(path.join(AGENT_CONTEXT_DIR, "stacks"));
|
|
909
|
+
const blueprintFileNames = await collectFileNames(path.join(AGENT_CONTEXT_DIR, "blueprints"));
|
|
910
|
+
const existingOnboardingReport = await loadOnboardingReportIfExists(resolvedTargetDirectoryPath);
|
|
911
|
+
const projectDetection = await detectProjectContext(resolvedTargetDirectoryPath);
|
|
912
|
+
|
|
913
|
+
const selectedProfileName = PROFILE_PRESETS[existingOnboardingReport?.selectedProfile]
|
|
914
|
+
? existingOnboardingReport.selectedProfile
|
|
915
|
+
: "balanced";
|
|
916
|
+
|
|
917
|
+
const selectedStackFileName = stackFileNames.includes(existingOnboardingReport?.selectedStack)
|
|
918
|
+
? existingOnboardingReport.selectedStack
|
|
919
|
+
: projectDetection.recommendedStackFileName || "typescript.md";
|
|
920
|
+
|
|
921
|
+
const selectedBlueprintFileName = blueprintFileNames.includes(existingOnboardingReport?.selectedBlueprint)
|
|
922
|
+
? existingOnboardingReport.selectedBlueprint
|
|
923
|
+
: BLUEPRINT_RECOMMENDATIONS[selectedStackFileName] || "api-nextjs.md";
|
|
924
|
+
|
|
925
|
+
const includeCiGuardrails = typeof existingOnboardingReport?.ciGuardrailsEnabled === "boolean"
|
|
926
|
+
? existingOnboardingReport.ciGuardrailsEnabled
|
|
927
|
+
: true;
|
|
928
|
+
|
|
929
|
+
const currentRulesPath = path.join(resolvedTargetDirectoryPath, ".cursorrules");
|
|
930
|
+
const currentRulesContent = await pathExists(currentRulesPath)
|
|
931
|
+
? await fs.readFile(currentRulesPath, "utf8")
|
|
932
|
+
: "";
|
|
933
|
+
|
|
934
|
+
const plannedRulesContent = await buildCompiledRulesContent({
|
|
935
|
+
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
936
|
+
selectedProfileName,
|
|
937
|
+
selectedStackFileName,
|
|
938
|
+
selectedBlueprintFileName,
|
|
939
|
+
includeCiGuardrails,
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
const isRulesContentChanged = currentRulesContent !== plannedRulesContent;
|
|
943
|
+
const currentRuleLineCount = currentRulesContent ? currentRulesContent.split(/\r?\n/).length : 0;
|
|
944
|
+
const plannedRuleLineCount = plannedRulesContent.split(/\r?\n/).length;
|
|
945
|
+
|
|
946
|
+
console.log("\nUpgrade preview:");
|
|
947
|
+
console.log(`- Target directory: ${resolvedTargetDirectoryPath}`);
|
|
948
|
+
console.log(`- Profile: ${toTitleCase(selectedProfileName)}`);
|
|
949
|
+
console.log(`- Stack: ${toTitleCase(selectedStackFileName)}`);
|
|
950
|
+
console.log(`- Blueprint: ${toTitleCase(selectedBlueprintFileName)}`);
|
|
951
|
+
console.log(`- CI/CD guardrails: ${includeCiGuardrails ? "enabled" : "disabled"}`);
|
|
952
|
+
console.log(`- Existing rules lines: ${currentRuleLineCount}`);
|
|
953
|
+
console.log(`- Planned rules lines: ${plannedRuleLineCount}`);
|
|
954
|
+
console.log(`- Rules changed: ${isRulesContentChanged ? "yes" : "no"}`);
|
|
955
|
+
|
|
956
|
+
if (upgradeOptions.dryRun) {
|
|
957
|
+
console.log("\nDry run enabled. No files were modified.");
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const shouldApplyUpgrade = upgradeOptions.skipConfirmation
|
|
962
|
+
? true
|
|
963
|
+
: await askYesNo("Apply upgrade and write migrated files?", userInterface, true);
|
|
964
|
+
|
|
965
|
+
if (!shouldApplyUpgrade) {
|
|
966
|
+
console.log("Upgrade cancelled by user.");
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
await fs.writeFile(currentRulesPath, plannedRulesContent, "utf8");
|
|
971
|
+
await fs.writeFile(path.join(resolvedTargetDirectoryPath, ".windsurfrules"), plannedRulesContent, "utf8");
|
|
972
|
+
await writeSelectedPolicy(resolvedTargetDirectoryPath, selectedProfileName);
|
|
973
|
+
|
|
974
|
+
const setupDurationMs = Date.now() - setupStartedAt;
|
|
975
|
+
await writeOnboardingReport({
|
|
976
|
+
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
977
|
+
selectedProfileName,
|
|
978
|
+
selectedProfilePack: existingOnboardingReport?.selectedProfilePack || null,
|
|
979
|
+
selectedStackFileName,
|
|
980
|
+
selectedBlueprintFileName,
|
|
981
|
+
includeCiGuardrails,
|
|
982
|
+
setupDurationMs,
|
|
983
|
+
projectDetection,
|
|
984
|
+
operationMode: "upgrade",
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
console.log("\nUpgrade complete.");
|
|
988
|
+
console.log(`- Rules rewritten: ${isRulesContentChanged ? "yes" : "no (metadata refreshed)"}`);
|
|
989
|
+
console.log(`- Setup time: ${formatDuration(setupDurationMs)}`);
|
|
990
|
+
console.log("- Updated files: .cursorrules, .windsurfrules, .agent-context/state/onboarding-report.json");
|
|
991
|
+
} finally {
|
|
992
|
+
userInterface.close();
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
async function main() {
|
|
997
|
+
const commandArgument = process.argv[2];
|
|
998
|
+
const commandArguments = process.argv.slice(3);
|
|
999
|
+
|
|
1000
|
+
if (!commandArgument || commandArgument === "--help" || commandArgument === "-h") {
|
|
1001
|
+
printUsage();
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (commandArgument === "--version" || commandArgument === "-v") {
|
|
1006
|
+
console.log(CLI_VERSION);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
if (commandArgument !== "init" && commandArgument !== "upgrade") {
|
|
1011
|
+
console.error(`Unknown command: ${commandArgument}`);
|
|
1012
|
+
printUsage();
|
|
1013
|
+
exit(1);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (commandArgument === "upgrade") {
|
|
1017
|
+
const parsedUpgradeOptions = {
|
|
1018
|
+
targetDirectory: ".",
|
|
1019
|
+
dryRun: false,
|
|
1020
|
+
skipConfirmation: false,
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
for (let argumentIndex = 0; argumentIndex < commandArguments.length; argumentIndex++) {
|
|
1024
|
+
const currentArgument = commandArguments[argumentIndex];
|
|
1025
|
+
|
|
1026
|
+
if (!currentArgument.startsWith("--")) {
|
|
1027
|
+
parsedUpgradeOptions.targetDirectory = currentArgument;
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (currentArgument === "--dry-run") {
|
|
1032
|
+
parsedUpgradeOptions.dryRun = true;
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (currentArgument === "--yes") {
|
|
1037
|
+
parsedUpgradeOptions.skipConfirmation = true;
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
throw new Error(`Unknown option: ${currentArgument}`);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
await runUpgradeCommand(parsedUpgradeOptions.targetDirectory, parsedUpgradeOptions);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
const parsedInitOptions = {
|
|
1049
|
+
targetDirectory: ".",
|
|
1050
|
+
profile: undefined,
|
|
1051
|
+
profilePack: undefined,
|
|
1052
|
+
stack: undefined,
|
|
1053
|
+
blueprint: undefined,
|
|
1054
|
+
ci: undefined,
|
|
1055
|
+
newbie: false,
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
for (let argumentIndex = 0; argumentIndex < commandArguments.length; argumentIndex++) {
|
|
1059
|
+
const currentArgument = commandArguments[argumentIndex];
|
|
1060
|
+
|
|
1061
|
+
if (!currentArgument.startsWith("--")) {
|
|
1062
|
+
parsedInitOptions.targetDirectory = currentArgument;
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (currentArgument === "--profile") {
|
|
1067
|
+
parsedInitOptions.profile = matchProfileNameFromInput(commandArguments[argumentIndex + 1] || "");
|
|
1068
|
+
argumentIndex += 1;
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (currentArgument.startsWith("--profile=")) {
|
|
1073
|
+
parsedInitOptions.profile = matchProfileNameFromInput(currentArgument.split("=")[1]);
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (currentArgument === "--profile-pack") {
|
|
1078
|
+
parsedInitOptions.profilePack = commandArguments[argumentIndex + 1];
|
|
1079
|
+
argumentIndex += 1;
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if (currentArgument.startsWith("--profile-pack=")) {
|
|
1084
|
+
parsedInitOptions.profilePack = currentArgument.split("=")[1];
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
if (currentArgument === "--stack") {
|
|
1089
|
+
parsedInitOptions.stack = commandArguments[argumentIndex + 1];
|
|
1090
|
+
argumentIndex += 1;
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
if (currentArgument.startsWith("--stack=")) {
|
|
1095
|
+
parsedInitOptions.stack = currentArgument.split("=")[1];
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
if (currentArgument === "--blueprint") {
|
|
1100
|
+
parsedInitOptions.blueprint = commandArguments[argumentIndex + 1];
|
|
1101
|
+
argumentIndex += 1;
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (currentArgument.startsWith("--blueprint=")) {
|
|
1106
|
+
parsedInitOptions.blueprint = currentArgument.split("=")[1];
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
if (currentArgument === "--ci") {
|
|
1111
|
+
const ciRawValue = commandArguments[argumentIndex + 1];
|
|
1112
|
+
parsedInitOptions.ci = ciRawValue?.toLowerCase() === "true";
|
|
1113
|
+
argumentIndex += 1;
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
if (currentArgument.startsWith("--ci=")) {
|
|
1118
|
+
parsedInitOptions.ci = currentArgument.split("=")[1]?.toLowerCase() === "true";
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (currentArgument === "--newbie") {
|
|
1123
|
+
parsedInitOptions.newbie = true;
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
throw new Error(`Unknown option: ${currentArgument}`);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
if (parsedInitOptions.newbie && parsedInitOptions.profile && parsedInitOptions.profile !== "beginner") {
|
|
1131
|
+
throw new Error("--newbie can only be combined with --profile beginner");
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
await runInitCommand(parsedInitOptions.targetDirectory, {
|
|
1135
|
+
profile: parsedInitOptions.profile,
|
|
1136
|
+
profilePack: parsedInitOptions.profilePack,
|
|
1137
|
+
stack: parsedInitOptions.stack,
|
|
1138
|
+
blueprint: parsedInitOptions.blueprint,
|
|
1139
|
+
ci: parsedInitOptions.ci,
|
|
1140
|
+
newbie: parsedInitOptions.newbie,
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
main().catch((error) => {
|
|
1145
|
+
console.error("CLI failed:", error);
|
|
1146
|
+
exit(1);
|
|
1147
|
+
});
|