@bantay/cli 0.1.1 → 0.3.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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@bantay/cli",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Write down the rules your system must never break. We enforce them on every PR.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "bantay": "./src/cli.ts"
7
+ "bantay": "src/cli.ts"
8
8
  },
9
9
  "files": [
10
10
  "src",
@@ -28,7 +28,7 @@
28
28
  "license": "MIT",
29
29
  "repository": {
30
30
  "type": "git",
31
- "url": "https://github.com/zcancio/bantay.git"
31
+ "url": "git+https://github.com/zcancio/bantay.git"
32
32
  },
33
33
  "dependencies": {
34
34
  "js-yaml": "^4.1.0"
@@ -0,0 +1,88 @@
1
+ import { readdir } from "fs/promises";
2
+ import { join } from "path";
3
+
4
+ export interface DiscoveryResult {
5
+ found: string[];
6
+ error?: string;
7
+ }
8
+
9
+ export interface ResolvedAidePath {
10
+ path: string;
11
+ filename: string;
12
+ }
13
+
14
+ /**
15
+ * Discover .aide files in a directory
16
+ */
17
+ export async function discoverAideFiles(cwd: string): Promise<DiscoveryResult> {
18
+ try {
19
+ const files = await readdir(cwd);
20
+ const aideFiles = files.filter((f) => f.endsWith(".aide"));
21
+ return { found: aideFiles };
22
+ } catch (error) {
23
+ return { found: [], error: error instanceof Error ? error.message : String(error) };
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Resolve the aide file path for a project directory
29
+ * - If explicitPath is provided, use it
30
+ * - Otherwise, glob for *.aide in projectPath
31
+ * - If exactly one found, use it
32
+ * - If multiple found, throw error
33
+ * - If none found, throw error
34
+ *
35
+ * @param projectPath - The project directory to search in
36
+ * @param explicitPath - Optional explicit path to an aide file
37
+ * @returns The resolved aide file path
38
+ * @throws Error if no aide file found or multiple found without explicit path
39
+ */
40
+ export async function resolveAidePath(
41
+ projectPath: string,
42
+ explicitPath?: string
43
+ ): Promise<ResolvedAidePath> {
44
+ // If explicit path provided, use it
45
+ if (explicitPath) {
46
+ const fullPath = explicitPath.startsWith("/")
47
+ ? explicitPath
48
+ : join(projectPath, explicitPath);
49
+ const filename = explicitPath.split("/").pop() || explicitPath;
50
+ return { path: fullPath, filename };
51
+ }
52
+
53
+ // Auto-discover
54
+ const { found, error } = await discoverAideFiles(projectPath);
55
+
56
+ if (error) {
57
+ throw new Error(`Error discovering aide files: ${error}`);
58
+ }
59
+
60
+ if (found.length === 0) {
61
+ throw new Error("No .aide file found. Run 'bantay aide init' to create one.");
62
+ }
63
+
64
+ if (found.length > 1) {
65
+ throw new Error(
66
+ `Multiple .aide files found. Specify one with --aide <path>\nFound: ${found.join(", ")}`
67
+ );
68
+ }
69
+
70
+ return {
71
+ path: join(projectPath, found[0]),
72
+ filename: found[0],
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Try to resolve aide path, returning null if not found (non-throwing version)
78
+ */
79
+ export async function tryResolveAidePath(
80
+ projectPath: string,
81
+ explicitPath?: string
82
+ ): Promise<ResolvedAidePath | null> {
83
+ try {
84
+ return await resolveAidePath(projectPath, explicitPath);
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
package/src/aide/index.ts CHANGED
@@ -16,6 +16,15 @@ import {
16
16
  // Re-export types
17
17
  export type { AideTree, Entity, Relationship } from "./types";
18
18
 
19
+ // Re-export discovery functions
20
+ export {
21
+ discoverAideFiles,
22
+ resolveAidePath,
23
+ tryResolveAidePath,
24
+ type DiscoveryResult,
25
+ type ResolvedAidePath,
26
+ } from "./discovery";
27
+
19
28
  /**
20
29
  * Read and parse a .aide YAML file
21
30
  */
package/src/cli.ts CHANGED
@@ -3,6 +3,7 @@ import { runInit } from "./commands/init";
3
3
  import { runCheck, formatCheckResults, formatCheckResultsJson } from "./commands/check";
4
4
  import { checkAllPrerequisites } from "./prerequisites";
5
5
  import {
6
+ handleAideInit,
6
7
  handleAideAdd,
7
8
  handleAideUpdate,
8
9
  handleAideRemove,
@@ -10,9 +11,14 @@ import {
10
11
  handleAideShow,
11
12
  handleAideValidate,
12
13
  handleAideLock,
14
+ handleAideDiff,
13
15
  printAideHelp,
14
16
  } from "./commands/aide";
15
- import { exportInvariants, exportClaude, exportCursor, exportAll } from "./export";
17
+ import { exportInvariants, exportClaude, exportCursor, exportCodex, exportAll } from "./export";
18
+ import { runStatus, formatStatus } from "./commands/status";
19
+ import { runCi, type CiOptions } from "./commands/ci";
20
+ import { runTasks, formatTasks } from "./commands/tasks";
21
+ import { handleDiff } from "./commands/diff";
16
22
 
17
23
  const args = process.argv.slice(2);
18
24
  const command = args[0];
@@ -33,10 +39,15 @@ async function main() {
33
39
  } else if (command === "aide") {
34
40
  await handleAide(args.slice(1));
35
41
  } else if (command === "ci") {
36
- console.error("bantay ci: Not yet implemented");
37
- process.exit(1);
42
+ await handleCi(args.slice(1));
38
43
  } else if (command === "export") {
39
44
  await handleExport(args.slice(1));
45
+ } else if (command === "status") {
46
+ await handleStatus(args.slice(1));
47
+ } else if (command === "tasks") {
48
+ await handleTasks(args.slice(1));
49
+ } else if (command === "diff") {
50
+ await handleDiff(args.slice(1));
40
51
  } else {
41
52
  console.error(`Unknown command: ${command}`);
42
53
  console.error('Run "bantay help" for usage information.');
@@ -69,24 +80,34 @@ Usage: bantay <command> [options]
69
80
  Commands:
70
81
  init Initialize Bantay in the current project
71
82
  check Check all invariants against the codebase
83
+ diff Show classified aide changes (wraps aide diff)
72
84
  aide Manage the aide entity tree (add, remove, link, show, validate, lock)
73
85
  ci Generate CI workflow configuration
74
86
  export Export invariants to agent context files
87
+ status Show scenario implementation status
88
+ tasks Generate task list from aide CUJs
75
89
 
76
90
  Options:
77
91
  -h, --help Show this help message
78
92
 
79
93
  Examples:
80
94
  bantay init Initialize in current directory
95
+ bantay init --force Regenerate slash commands
81
96
  bantay check Run full invariant check
82
97
  bantay check --diff HEAD~1 Check only affected invariants
98
+ bantay diff Show classified aide changes
99
+ bantay diff --json Output changes as JSON
83
100
  bantay aide show Show the aide entity tree
84
101
  bantay aide add inv_test --parent invariants --prop "statement=Test"
85
102
  bantay ci --github-actions Generate GitHub Actions workflow
86
- bantay export all Export all targets
87
- bantay export invariants Generate invariants.md from bantay.aide
88
- bantay export claude Export to CLAUDE.md
89
- bantay export cursor Export to .cursorrules
103
+ bantay export all Export all targets
104
+ bantay export invariants Generate invariants.md from bantay.aide
105
+ bantay export claude Export to CLAUDE.md
106
+ bantay export cursor Export to .cursorrules
107
+ bantay status Show scenario implementation status
108
+ bantay status --json Output as JSON
109
+ bantay tasks Generate tasks for changed CUJs (requires lock)
110
+ bantay tasks --all Generate tasks for all CUJs
90
111
 
91
112
  Run "bantay aide help" for aide subcommand details.
92
113
  `);
@@ -95,6 +116,7 @@ Run "bantay aide help" for aide subcommand details.
95
116
  async function handleInit(args: string[]) {
96
117
  const projectPath = process.cwd();
97
118
  const regenerateConfig = args.includes("--regenerate-config");
119
+ const force = args.includes("--force");
98
120
  const dryRun = args.includes("--dry-run");
99
121
 
100
122
  console.log("Initializing Bantay...\n");
@@ -105,7 +127,7 @@ async function handleInit(args: string[]) {
105
127
  }
106
128
 
107
129
  try {
108
- const result = await runInit(projectPath, { regenerateConfig });
130
+ const result = await runInit(projectPath, { regenerateConfig, force });
109
131
 
110
132
  // Display detection results
111
133
  console.log("Stack Detection:");
@@ -229,14 +251,16 @@ async function handleExport(args: string[]) {
229
251
  console.error("Usage: bantay export <target>");
230
252
  console.error("");
231
253
  console.error("Targets:");
232
- console.error(" all Export all targets (invariants, claude, cursor)");
254
+ console.error(" all Export all targets (invariants, claude, cursor, codex)");
233
255
  console.error(" invariants Generate invariants.md from bantay.aide");
234
256
  console.error(" claude Export to CLAUDE.md with section markers");
235
257
  console.error(" cursor Export to .cursorrules with section markers");
258
+ console.error(" codex Export to AGENTS.md with section markers");
236
259
  console.error("");
237
260
  console.error("Examples:");
238
261
  console.error(" bantay export invariants");
239
262
  console.error(" bantay export claude");
263
+ console.error(" bantay export codex");
240
264
  console.error(" bantay export --target cursor");
241
265
  process.exit(1);
242
266
  }
@@ -262,9 +286,13 @@ async function handleExport(args: string[]) {
262
286
  const result = await exportCursor(projectPath, { dryRun });
263
287
  console.log(`Exported to ${result.outputPath}`);
264
288
  console.log(` ${result.bytesWritten} bytes written`);
289
+ } else if (target === "codex") {
290
+ const result = await exportCodex(projectPath, { dryRun });
291
+ console.log(`Exported to ${result.outputPath}`);
292
+ console.log(` ${result.bytesWritten} bytes written`);
265
293
  } else {
266
294
  console.error(`Unknown export target: ${target}`);
267
- console.error('Valid targets: all, invariants, claude, cursor');
295
+ console.error('Valid targets: all, invariants, claude, cursor, codex');
268
296
  process.exit(1);
269
297
  }
270
298
 
@@ -283,6 +311,96 @@ async function handleExport(args: string[]) {
283
311
  }
284
312
  }
285
313
 
314
+ async function handleCi(args: string[]) {
315
+ const projectPath = process.cwd();
316
+
317
+ // Parse provider from args
318
+ const hasGitHub = args.includes("--github-actions") || args.includes("--github");
319
+ const hasGitLab = args.includes("--gitlab");
320
+ const force = args.includes("--force");
321
+
322
+ let provider: "github-actions" | "gitlab" | "generic";
323
+
324
+ if (hasGitHub) {
325
+ provider = "github-actions";
326
+ } else if (hasGitLab) {
327
+ provider = "gitlab";
328
+ } else {
329
+ provider = "generic";
330
+ }
331
+
332
+ try {
333
+ const result = await runCi(projectPath, { provider, force });
334
+
335
+ if (result.alreadyExists) {
336
+ console.error(`${result.outputPath} already exists.`);
337
+ console.error("Use --force to overwrite.");
338
+ process.exit(1);
339
+ }
340
+
341
+ if (provider === "generic") {
342
+ console.log(result.content);
343
+ } else {
344
+ console.log(`Generated ${result.outputPath}`);
345
+ }
346
+
347
+ process.exit(0);
348
+ } catch (error) {
349
+ if (error instanceof Error) {
350
+ console.error(`Error: ${error.message}`);
351
+ } else {
352
+ console.error("Error running ci:", error);
353
+ }
354
+ process.exit(1);
355
+ }
356
+ }
357
+
358
+ async function handleStatus(args: string[]) {
359
+ const projectPath = process.cwd();
360
+ const jsonOutput = args.includes("--json");
361
+
362
+ try {
363
+ const result = await runStatus(projectPath, { json: jsonOutput });
364
+
365
+ if (jsonOutput) {
366
+ console.log(JSON.stringify(result, null, 2));
367
+ } else {
368
+ console.log(formatStatus(result));
369
+ }
370
+
371
+ process.exit(0);
372
+ } catch (error) {
373
+ if (error instanceof Error) {
374
+ console.error(`Error: ${error.message}`);
375
+ } else {
376
+ console.error("Error running status:", error);
377
+ }
378
+ process.exit(1);
379
+ }
380
+ }
381
+
382
+ async function handleTasks(args: string[]) {
383
+ const projectPath = process.cwd();
384
+ const allFlag = args.includes("--all");
385
+
386
+ // Parse --aide option
387
+ const aideIndex = args.indexOf("--aide");
388
+ const aideFile = aideIndex !== -1 ? args[aideIndex + 1] : undefined;
389
+
390
+ try {
391
+ const result = await runTasks(projectPath, { all: allFlag, aide: aideFile });
392
+ console.log(formatTasks(result));
393
+ process.exit(0);
394
+ } catch (error) {
395
+ if (error instanceof Error) {
396
+ console.error(`Error: ${error.message}`);
397
+ } else {
398
+ console.error("Error running tasks:", error);
399
+ }
400
+ process.exit(1);
401
+ }
402
+ }
403
+
286
404
  async function handleAide(args: string[]) {
287
405
  const subcommand = args[0];
288
406
 
@@ -293,7 +411,9 @@ async function handleAide(args: string[]) {
293
411
 
294
412
  const subArgs = args.slice(1);
295
413
 
296
- if (subcommand === "add") {
414
+ if (subcommand === "init") {
415
+ await handleAideInit(subArgs);
416
+ } else if (subcommand === "add") {
297
417
  await handleAideAdd(subArgs);
298
418
  } else if (subcommand === "update") {
299
419
  await handleAideUpdate(subArgs);
@@ -307,6 +427,8 @@ async function handleAide(args: string[]) {
307
427
  await handleAideValidate(subArgs);
308
428
  } else if (subcommand === "lock") {
309
429
  await handleAideLock(subArgs);
430
+ } else if (subcommand === "diff") {
431
+ await handleAideDiff(subArgs);
310
432
  } else {
311
433
  console.error(`Unknown aide subcommand: ${subcommand}`);
312
434
  console.error('Run "bantay aide help" for usage information.');