lean-spec 0.2.7-dev.20251127024801 → 0.2.7-dev.20251127055844

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.
@@ -16,7 +16,7 @@ import stripAnsi from 'strip-ansi';
16
16
  import matter from 'gray-matter';
17
17
  import yaml from 'js-yaml';
18
18
  import dayjs3 from 'dayjs';
19
- import { select, checkbox } from '@inquirer/prompts';
19
+ import { select, checkbox, confirm } from '@inquirer/prompts';
20
20
  import { marked } from 'marked';
21
21
  import { markedTerminal } from 'marked-terminal';
22
22
 
@@ -4494,6 +4494,11 @@ var AI_TOOL_CONFIGS = {
4494
4494
  detection: {
4495
4495
  commands: ["aider"],
4496
4496
  configDirs: [".aider"]
4497
+ },
4498
+ cli: {
4499
+ command: "aider",
4500
+ promptFlag: "--message"
4501
+ // Aider doesn't have a simple allow-all flag, uses different interaction model
4497
4502
  }
4498
4503
  },
4499
4504
  claude: {
@@ -4505,6 +4510,15 @@ var AI_TOOL_CONFIGS = {
4505
4510
  commands: ["claude"],
4506
4511
  configDirs: [".claude"],
4507
4512
  envVars: ["ANTHROPIC_API_KEY"]
4513
+ },
4514
+ cli: {
4515
+ command: "claude",
4516
+ promptFlag: "-p",
4517
+ // -p is the print/non-interactive flag
4518
+ promptIsPositional: true,
4519
+ // Prompt is positional argument
4520
+ allowToolsFlag: "--permission-mode acceptEdits"
4521
+ // Auto-accept edit operations
4508
4522
  }
4509
4523
  },
4510
4524
  codex: {
@@ -4527,6 +4541,11 @@ var AI_TOOL_CONFIGS = {
4527
4541
  detection: {
4528
4542
  commands: ["copilot"],
4529
4543
  envVars: ["GITHUB_TOKEN"]
4544
+ },
4545
+ cli: {
4546
+ command: "copilot",
4547
+ promptFlag: "-p",
4548
+ allowToolsFlag: "--allow-all-tools"
4530
4549
  }
4531
4550
  },
4532
4551
  cursor: {
@@ -4557,6 +4576,13 @@ var AI_TOOL_CONFIGS = {
4557
4576
  commands: ["gemini"],
4558
4577
  configDirs: [".gemini"],
4559
4578
  envVars: ["GOOGLE_API_KEY", "GEMINI_API_KEY"]
4579
+ },
4580
+ cli: {
4581
+ command: "gemini",
4582
+ promptFlag: "-p",
4583
+ // Note: deprecated but still works
4584
+ allowToolsFlag: "-y"
4585
+ // YOLO mode
4560
4586
  }
4561
4587
  },
4562
4588
  opencode: {
@@ -4865,6 +4891,122 @@ async function getProjectName2(cwd) {
4865
4891
  }
4866
4892
  return path17.basename(cwd);
4867
4893
  }
4894
+ function buildMergeCommand(cli, promptPath) {
4895
+ const prompt = `Follow the instructions in ${promptPath} to consolidate AGENTS.md. Read the prompt file, then edit AGENTS.md with the merged content.`;
4896
+ const args = [];
4897
+ if (cli.promptIsPositional) {
4898
+ args.push(prompt);
4899
+ if (cli.promptFlag) {
4900
+ args.push(cli.promptFlag);
4901
+ }
4902
+ } else {
4903
+ args.push(cli.promptFlag);
4904
+ args.push(prompt);
4905
+ }
4906
+ if (cli.allowToolsFlag) {
4907
+ const flagParts = cli.allowToolsFlag.split(" ");
4908
+ args.push(...flagParts);
4909
+ }
4910
+ return { command: cli.command, args };
4911
+ }
4912
+ async function executeMergeWithAI(cwd, promptPath, tool, timeoutMs = 12e4) {
4913
+ const config = AI_TOOL_CONFIGS[tool];
4914
+ if (!config.cli) {
4915
+ return {
4916
+ success: false,
4917
+ error: `Tool ${tool} does not have CLI configuration`
4918
+ };
4919
+ }
4920
+ const { command, args } = buildMergeCommand(config.cli, promptPath);
4921
+ const quotedArgs = args.map((arg) => {
4922
+ if (arg.includes(" ") || arg.includes('"') || arg.includes("'")) {
4923
+ return `'${arg.replace(/'/g, "'\\''")}'`;
4924
+ }
4925
+ return arg;
4926
+ });
4927
+ const fullCommand = `${command} ${quotedArgs.join(" ")}`;
4928
+ return new Promise((resolve3) => {
4929
+ let output = "";
4930
+ let errorOutput = "";
4931
+ let timedOut = false;
4932
+ const child = spawn(fullCommand, [], {
4933
+ cwd,
4934
+ shell: true,
4935
+ stdio: ["inherit", "pipe", "pipe"]
4936
+ });
4937
+ const timeout = setTimeout(() => {
4938
+ timedOut = true;
4939
+ child.kill("SIGTERM");
4940
+ }, timeoutMs);
4941
+ child.stdout?.on("data", (data) => {
4942
+ output += data.toString();
4943
+ process.stdout.write(data);
4944
+ });
4945
+ child.stderr?.on("data", (data) => {
4946
+ errorOutput += data.toString();
4947
+ process.stderr.write(data);
4948
+ });
4949
+ child.on("close", (code) => {
4950
+ clearTimeout(timeout);
4951
+ if (timedOut) {
4952
+ resolve3({
4953
+ success: false,
4954
+ output,
4955
+ error: "Command timed out",
4956
+ timedOut: true
4957
+ });
4958
+ } else if (code === 0) {
4959
+ resolve3({
4960
+ success: true,
4961
+ output
4962
+ });
4963
+ } else {
4964
+ resolve3({
4965
+ success: false,
4966
+ output,
4967
+ error: errorOutput || `Process exited with code ${code}`
4968
+ });
4969
+ }
4970
+ });
4971
+ child.on("error", (err) => {
4972
+ clearTimeout(timeout);
4973
+ resolve3({
4974
+ success: false,
4975
+ error: err.message
4976
+ });
4977
+ });
4978
+ });
4979
+ }
4980
+ async function getCliCapableDetectedTools() {
4981
+ const detectionResults = await detectInstalledAITools();
4982
+ const priorityOrder = ["copilot", "gemini", "claude", "aider", "codex"];
4983
+ const detected = detectionResults.filter((r) => r.detected && AI_TOOL_CONFIGS[r.tool].cli).map((r) => ({
4984
+ tool: r.tool,
4985
+ config: AI_TOOL_CONFIGS[r.tool],
4986
+ reasons: r.reasons
4987
+ }));
4988
+ detected.sort((a, b) => {
4989
+ const aIndex = priorityOrder.indexOf(a.tool);
4990
+ const bIndex = priorityOrder.indexOf(b.tool);
4991
+ const aPriority = aIndex === -1 ? 999 : aIndex;
4992
+ const bPriority = bIndex === -1 ? 999 : bIndex;
4993
+ return aPriority - bPriority;
4994
+ });
4995
+ return detected;
4996
+ }
4997
+ function getDisplayCommand(tool, promptPath) {
4998
+ const config = AI_TOOL_CONFIGS[tool];
4999
+ if (!config.cli) return "";
5000
+ const { command, args } = buildMergeCommand(config.cli, promptPath);
5001
+ const displayArgs = args.map((arg, index) => {
5002
+ const isPromptArg = config.cli.promptIsPositional ? index === 0 : index === 1;
5003
+ if (isPromptArg && arg.includes(" ")) {
5004
+ return `"${arg}"`;
5005
+ }
5006
+ return arg;
5007
+ });
5008
+ return `${command} ${displayArgs.join(" ")}`;
5009
+ }
4868
5010
 
4869
5011
  // src/utils/examples.ts
4870
5012
  var EXAMPLES = {
@@ -4916,6 +5058,55 @@ function exampleExists(name) {
4916
5058
  var __dirname = path17.dirname(fileURLToPath(import.meta.url));
4917
5059
  var TEMPLATES_DIR = path17.join(__dirname, "..", "templates");
4918
5060
  var EXAMPLES_DIR = path17.join(TEMPLATES_DIR, "examples");
5061
+ async function attemptAutoMerge(cwd, promptPath, autoExecute) {
5062
+ const cliTools = await getCliCapableDetectedTools();
5063
+ if (cliTools.length === 0) {
5064
+ return false;
5065
+ }
5066
+ const tool = cliTools[0];
5067
+ const displayCmd = getDisplayCommand(tool.tool, promptPath);
5068
+ console.log("");
5069
+ console.log(chalk20.cyan(`\u{1F50D} Detected AI CLI: ${tool.config.description}`));
5070
+ for (const reason of tool.reasons) {
5071
+ console.log(chalk20.gray(` \u2514\u2500 ${reason}`));
5072
+ }
5073
+ console.log("");
5074
+ console.log(chalk20.gray(`Command: ${displayCmd}`));
5075
+ console.log("");
5076
+ let shouldExecute = autoExecute;
5077
+ if (!autoExecute) {
5078
+ shouldExecute = await confirm({
5079
+ message: "Run merge automatically using detected AI CLI?",
5080
+ default: true
5081
+ });
5082
+ }
5083
+ if (!shouldExecute) {
5084
+ console.log(chalk20.gray("Skipping auto-merge. Run the command above manually to merge."));
5085
+ return false;
5086
+ }
5087
+ console.log("");
5088
+ console.log(chalk20.cyan("\u{1F916} Running AI-assisted merge..."));
5089
+ console.log(chalk20.gray(" (This may take a moment)"));
5090
+ console.log("");
5091
+ const result = await executeMergeWithAI(cwd, promptPath, tool.tool);
5092
+ if (result.success) {
5093
+ console.log("");
5094
+ console.log(chalk20.green("\u2713 AGENTS.md merged successfully!"));
5095
+ console.log(chalk20.gray(" Review changes: git diff AGENTS.md"));
5096
+ return true;
5097
+ } else if (result.timedOut) {
5098
+ console.log("");
5099
+ console.log(chalk20.yellow("\u26A0 Merge timed out. Try running the command manually:"));
5100
+ console.log(chalk20.gray(` ${displayCmd}`));
5101
+ return false;
5102
+ } else {
5103
+ console.log("");
5104
+ console.log(chalk20.yellow(`\u26A0 Auto-merge encountered an issue: ${result.error}`));
5105
+ console.log(chalk20.gray(" Try running the command manually:"));
5106
+ console.log(chalk20.gray(` ${displayCmd}`));
5107
+ return false;
5108
+ }
5109
+ }
4919
5110
  function initCommand() {
4920
5111
  return new Command("init").description("Initialize LeanSpec in current directory").option("-y, --yes", "Skip prompts and use defaults (quick start with standard template)").option("--template <name>", "Use specific template (standard or detailed)").option("--example [name]", "Scaffold an example project for tutorials (interactive if no name provided)").option("--name <dirname>", "Custom directory name for example project").option("--list", "List available example projects").option("--agent-tools <tools>", 'AI tools to create symlinks for (comma-separated: claude,gemini,copilot or "all" or "none")').action(async (options) => {
4921
5112
  if (options.list) {
@@ -5129,6 +5320,7 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
5129
5320
  console.log(chalk20.green("\u2713 Created .lean-spec/config.json"));
5130
5321
  const existingFiles = await detectExistingSystemPrompts(cwd);
5131
5322
  let skipFiles = [];
5323
+ let mergeCompleted = false;
5132
5324
  if (existingFiles.length > 0) {
5133
5325
  console.log("");
5134
5326
  console.log(chalk20.yellow(`Found existing: ${existingFiles.join(", ")}`));
@@ -5136,6 +5328,13 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
5136
5328
  console.log(chalk20.gray("Using AI-Assisted Merge for existing AGENTS.md"));
5137
5329
  const projectName2 = await getProjectName2(cwd);
5138
5330
  await handleExistingFiles("merge-ai", existingFiles, templateDir, cwd, { project_name: projectName2 });
5331
+ const promptPath = path17.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
5332
+ mergeCompleted = await attemptAutoMerge(
5333
+ cwd,
5334
+ promptPath,
5335
+ true
5336
+ /* skipPrompts */
5337
+ );
5139
5338
  } else {
5140
5339
  const action = await select({
5141
5340
  message: "How would you like to handle existing AGENTS.md?",
@@ -5166,11 +5365,19 @@ async function initProject(skipPrompts = false, templateOption, agentToolsOption
5166
5365
  await handleExistingFiles(action, existingFiles, templateDir, cwd, { project_name: projectName2 });
5167
5366
  if (action === "skip") {
5168
5367
  skipFiles = existingFiles;
5368
+ } else if (action === "merge-ai") {
5369
+ const promptPath = path17.join(cwd, ".lean-spec", "MERGE-AGENTS-PROMPT.md");
5370
+ mergeCompleted = await attemptAutoMerge(
5371
+ cwd,
5372
+ promptPath,
5373
+ false
5374
+ /* skipPrompts */
5375
+ );
5169
5376
  }
5170
5377
  }
5171
5378
  }
5172
5379
  const projectName = await getProjectName2(cwd);
5173
- if (!skipFiles.includes("AGENTS.md")) {
5380
+ if (!skipFiles.includes("AGENTS.md") && !mergeCompleted) {
5174
5381
  const agentsSourcePath = path17.join(templateDir, "AGENTS.md");
5175
5382
  const agentsTargetPath = path17.join(cwd, "AGENTS.md");
5176
5383
  try {
@@ -10997,5 +11204,5 @@ if (import.meta.url === `file://${process.argv[1]}`) {
10997
11204
  }
10998
11205
 
10999
11206
  export { agentCommand, analyzeCommand, archiveCommand, backfillCommand, boardCommand, checkCommand, compactCommand, createCommand, createMcpServer, depsCommand, examplesCommand, filesCommand, ganttCommand, initCommand, linkCommand, listCommand, mcpCommand, migrateCommand, openCommand, searchCommand, splitCommand, statsCommand, templatesCommand, timelineCommand, tokensCommand, uiCommand, unlinkCommand, updateCommand, validateCommand, viewCommand };
11000
- //# sourceMappingURL=chunk-7D64YGN6.js.map
11001
- //# sourceMappingURL=chunk-7D64YGN6.js.map
11207
+ //# sourceMappingURL=chunk-FTKNRIOE.js.map
11208
+ //# sourceMappingURL=chunk-FTKNRIOE.js.map