@attalabs/vinaya 0.7.1 → 0.8.1

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/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync17 } from "node:fs";
5
- import { dirname as dirname7, join as join19 } from "node:path";
4
+ import { readFileSync as readFileSync18 } from "node:fs";
5
+ import { dirname as dirname8, join as join20 } from "node:path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7
7
 
8
8
  // src/commands/archive.ts
@@ -332,6 +332,52 @@ var systemEnv3 = {
332
332
  ...process.env,
333
333
  PATH: [process.env.PATH, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].filter(Boolean).join(":")
334
334
  };
335
+ var defaultExec = () => execFileAsync3("git", ["remote", "get-url", "origin"], { env: systemEnv3, timeout: 5000 });
336
+ var cached;
337
+ var inflight;
338
+ async function resolveRepo(exec = defaultExec) {
339
+ if (cached !== undefined)
340
+ return cached;
341
+ const fromEnv = process.env.AEG_REPO;
342
+ if (fromEnv) {
343
+ const parsed = parseOwnerRepo(fromEnv);
344
+ if (parsed) {
345
+ cached = parsed;
346
+ return cached;
347
+ }
348
+ }
349
+ if (inflight)
350
+ return inflight;
351
+ inflight = (async () => {
352
+ try {
353
+ const { stdout } = await exec();
354
+ cached = parseGitRemoteUrl(stdout.trim());
355
+ return cached;
356
+ } catch (err) {
357
+ const message = err instanceof Error ? err.message : String(err);
358
+ console.warn(`[aeg-forge-state] resolveRepo: git remote lookup failed (will retry): ${message}`);
359
+ return null;
360
+ } finally {
361
+ inflight = undefined;
362
+ }
363
+ })();
364
+ return inflight;
365
+ }
366
+ function parseGitRemoteUrl(url) {
367
+ const ssh = url.match(/^git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
368
+ if (ssh?.[1] && ssh[2])
369
+ return { owner: ssh[1], repo: ssh[2] };
370
+ const https = url.match(/^https?:\/\/(?:[^@]+@)?github\.com\/([^/]+)\/(.+?)(?:\.git)?\/?$/);
371
+ if (https?.[1] && https[2])
372
+ return { owner: https[1], repo: https[2] };
373
+ return null;
374
+ }
375
+ function parseOwnerRepo(value) {
376
+ const match = value.match(/^([^/]+)\/(.+)$/);
377
+ if (!match?.[1] || !match[2])
378
+ return null;
379
+ return { owner: match[1], repo: match[2] };
380
+ }
335
381
  // ../../packages/aeg-core/src/state-machine-model.ts
336
382
  var FORGE_FACT_INPUTS = [
337
383
  {
@@ -378,6 +424,11 @@ var FORGE_FACT_INPUTS = [
378
424
  fact: "mergedAt",
379
425
  readsFrom: "PullRequest.mergedAt",
380
426
  meaning: "Timestamp for the coherence oracle grandfather cutoff. No derivation rule reads it."
427
+ },
428
+ {
429
+ fact: "closedByActor",
430
+ readsFrom: "ClosedEvent.actor.login (most recent timeline CLOSED_EVENT)",
431
+ meaning: "GitHub login that hand-closed the Issue. No task-status derivation rule reads it — it is consumed by dispatch-gate.ts/coherence-checks.ts A1 to recognize a hand-closed dependency as resolved (task vinaya-engine-v1 21, #99), not by this status model."
381
432
  }
382
433
  ];
383
434
  var DERIVED_STATUSES = [
@@ -880,6 +931,8 @@ import { fileURLToPath } from "node:url";
880
931
  function packageRoot(moduleUrl) {
881
932
  let dir = dirname(fileURLToPath(moduleUrl));
882
933
  while (!existsSync(join(dir, "package.json"))) {
934
+ if (existsSync(join(dir, ".git")))
935
+ break;
883
936
  const parent = dirname(dir);
884
937
  if (parent === dir)
885
938
  break;
@@ -1744,6 +1797,8 @@ function findLocalConfig() {
1744
1797
  const candidate = join3(dir, LOCAL_CONFIG_FILENAME);
1745
1798
  if (existsSync2(candidate))
1746
1799
  return candidate;
1800
+ if (existsSync2(join3(dir, ".git")))
1801
+ return null;
1747
1802
  const parent = dirname2(dir);
1748
1803
  if (parent === dir)
1749
1804
  return null;
@@ -5463,9 +5518,104 @@ async function quickstartCommand(args) {
5463
5518
  process.exit(await runQuickstart(args, realDeps6()));
5464
5519
  }
5465
5520
 
5521
+ // src/commands/studio.ts
5522
+ import { execFile as execFile5, spawn as spawn2 } from "node:child_process";
5523
+ import { existsSync as existsSync16, readFileSync as readFileSync16, renameSync } from "node:fs";
5524
+ import net from "node:net";
5525
+ import { dirname as dirname7, join as join18 } from "node:path";
5526
+ import { promisify as promisify5 } from "node:util";
5527
+
5528
+ // src/lib/studio-bundle.ts
5529
+ var STUDIO_NODE_MODULES_PACKED_DIRNAME = "_node_modules";
5530
+
5531
+ // src/commands/studio.ts
5532
+ var execFileAsync5 = promisify5(execFile5);
5533
+ function execFileForRepo(cwd) {
5534
+ return execFileAsync5("git", ["remote", "get-url", "origin"], { cwd, timeout: 5000 });
5535
+ }
5536
+ var PRIMARY_PORT = 3006;
5537
+ var FALLBACK_PORT = 3106;
5538
+ function resolveStudioTarget(cwd, moduleUrl = import.meta.url) {
5539
+ let dir = cwd;
5540
+ for (;; ) {
5541
+ const webDir = join18(dir, "apps", "vinaya", "web");
5542
+ const pkgPath = join18(webDir, "package.json");
5543
+ if (existsSync16(pkgPath)) {
5544
+ try {
5545
+ const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
5546
+ if (pkg.name === "@atta/vinaya-web") {
5547
+ return { kind: "workspace", webDir };
5548
+ }
5549
+ } catch {}
5550
+ }
5551
+ if (existsSync16(join18(dir, ".git")))
5552
+ break;
5553
+ const parent = dirname7(dir);
5554
+ if (parent === dir)
5555
+ break;
5556
+ dir = parent;
5557
+ }
5558
+ const standaloneWebDir = join18(packageRoot(moduleUrl), "studio-standalone", "apps", "vinaya", "web");
5559
+ if (existsSync16(join18(standaloneWebDir, "server.js"))) {
5560
+ return { kind: "package", packageDir: standaloneWebDir };
5561
+ }
5562
+ return { kind: "missing" };
5563
+ }
5564
+ function spawnDev(webDir, args) {
5565
+ return new Promise((resolve2) => {
5566
+ const child = spawn2("bun", ["run", "dev", ...args], { cwd: webDir, stdio: "inherit" });
5567
+ child.on("exit", (code) => resolve2(code ?? 0));
5568
+ });
5569
+ }
5570
+ function isPortFree(port) {
5571
+ return new Promise((resolve2) => {
5572
+ const tester = net.createServer();
5573
+ tester.once("error", () => resolve2(false));
5574
+ tester.once("listening", () => tester.close(() => resolve2(true)));
5575
+ tester.listen(port, "127.0.0.1");
5576
+ });
5577
+ }
5578
+ function ensureStudioNodeModules(bundleRoot) {
5579
+ const real = join18(bundleRoot, "node_modules");
5580
+ const packed = join18(bundleRoot, STUDIO_NODE_MODULES_PACKED_DIRNAME);
5581
+ if (!existsSync16(real) && existsSync16(packed)) {
5582
+ renameSync(packed, real);
5583
+ }
5584
+ }
5585
+ async function spawnStandalone(cwd, serverPath, bundleRoot) {
5586
+ ensureStudioNodeModules(bundleRoot);
5587
+ const repo = await resolveRepo(() => execFileForRepo(cwd));
5588
+ const primaryFree = await isPortFree(PRIMARY_PORT);
5589
+ const port = primaryFree ? PRIMARY_PORT : FALLBACK_PORT;
5590
+ if (!primaryFree) {
5591
+ console.info(`[studio] port ${PRIMARY_PORT} is taken — falling back to ${FALLBACK_PORT}`);
5592
+ }
5593
+ const env = { HOSTNAME: "127.0.0.1", ...process.env, PORT: String(port), VINAYA_REPO_ROOT: cwd };
5594
+ if (repo)
5595
+ env.AEG_REPO = `${repo.owner}/${repo.repo}`;
5596
+ return new Promise((resolve2) => {
5597
+ const child = spawn2("node", [serverPath], { cwd, stdio: "inherit", env });
5598
+ child.on("exit", (code) => resolve2(code ?? 0));
5599
+ });
5600
+ }
5601
+ async function runStudio(cwd, args, moduleUrl = import.meta.url) {
5602
+ const target = resolveStudioTarget(cwd, moduleUrl);
5603
+ switch (target.kind) {
5604
+ case "workspace":
5605
+ return spawnDev(target.webDir, args);
5606
+ case "package": {
5607
+ const bundleRoot = join18(target.packageDir, "..", "..", "..");
5608
+ return spawnStandalone(cwd, join18(target.packageDir, "server.js"), bundleRoot);
5609
+ }
5610
+ case "missing":
5611
+ console.error("Vinaya Studio isn't available in this install — no published @attalabs/vinaya build bundles the Studio app yet. Inside a checkout that contains Studio's source (apps/vinaya/web), `vinaya studio` runs it directly.");
5612
+ return 1;
5613
+ }
5614
+ }
5615
+
5466
5616
  // src/commands/upgrade.ts
5467
- import { existsSync as existsSync16, readFileSync as readFileSync16, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
5468
- import { join as join18 } from "node:path";
5617
+ import { existsSync as existsSync17, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
5618
+ import { join as join19 } from "node:path";
5469
5619
  function realDeps7() {
5470
5620
  return {
5471
5621
  detectRepo: detectGitRepo,
@@ -5483,12 +5633,12 @@ function flags2(args) {
5483
5633
  return { dryRun: args.includes("--dry-run"), yes: args.includes("--yes") };
5484
5634
  }
5485
5635
  function readManifest3(repoRoot) {
5486
- const p = join18(repoRoot, CONFIG_PATH);
5487
- if (!existsSync16(p))
5636
+ const p = join19(repoRoot, CONFIG_PATH);
5637
+ if (!existsSync17(p))
5488
5638
  return { kind: "missing" };
5489
5639
  let raw;
5490
5640
  try {
5491
- raw = JSON.parse(readFileSync16(p, "utf-8"));
5641
+ raw = JSON.parse(readFileSync17(p, "utf-8"));
5492
5642
  } catch (err) {
5493
5643
  return { kind: "invalid", error: `invalid JSON: ${err.message}` };
5494
5644
  }
@@ -5504,17 +5654,17 @@ function readManifest3(repoRoot) {
5504
5654
  return { kind: "ok", manifest: parsed.data.managed };
5505
5655
  }
5506
5656
  function writeManifestVersion(repoRoot, manifest) {
5507
- const configAbs = join18(repoRoot, CONFIG_PATH);
5508
- const seed = JSON.parse(readFileSync16(configAbs, "utf-8"));
5657
+ const configAbs = join19(repoRoot, CONFIG_PATH);
5658
+ const seed = JSON.parse(readFileSync17(configAbs, "utf-8"));
5509
5659
  const updated = { ...manifest, version: MANAGED_MANIFEST_VERSION };
5510
5660
  writeFileSync9(configAbs, `${JSON.stringify({ ...seed, managed: updated }, null, 2)}
5511
5661
  `, "utf-8");
5512
5662
  }
5513
5663
  function stripFor(repoRoot, path, marker, comment) {
5514
5664
  const abs2 = resolveManagedBlockPath(repoRoot, path);
5515
- if (!existsSync16(abs2))
5665
+ if (!existsSync17(abs2))
5516
5666
  return { path, marker, comment, present: false, removesHost: false };
5517
- const stripped = stripBlockFromContent(readFileSync16(abs2, "utf-8"), marker, comment);
5667
+ const stripped = stripBlockFromContent(readFileSync17(abs2, "utf-8"), marker, comment);
5518
5668
  if (stripped === null)
5519
5669
  return { path, marker, comment, present: false, removesHost: false };
5520
5670
  return { path, marker, comment, present: true, removesHost: blockStripLeavesEmpty(stripped) };
@@ -5541,9 +5691,9 @@ function planHookRouting(repoRoot, manifest, recorded, hooksPathValue) {
5541
5691
  const blocked = [];
5542
5692
  for (const b of legacy) {
5543
5693
  const abs2 = resolveManagedBlockPath(repoRoot, b.path);
5544
- if (!existsSync16(abs2))
5694
+ if (!existsSync17(abs2))
5545
5695
  continue;
5546
- const stripped = stripBlockFromContent(readFileSync16(abs2, "utf-8"), b.marker, b.comment);
5696
+ const stripped = stripBlockFromContent(readFileSync17(abs2, "utf-8"), b.marker, b.comment);
5547
5697
  if (stripped === null)
5548
5698
  blocked.push(`${b.path} exists without vinaya's managed block`);
5549
5699
  else if (!blockStripLeavesEmpty(stripped))
@@ -5578,8 +5728,8 @@ function planUpgrade(ops, repoRoot, manifest, routing) {
5578
5728
  const ownedBlocks = new Set(manifest.blocks.map((b) => blockKey(b.path, b.marker)));
5579
5729
  for (const op of ops) {
5580
5730
  if (op.kind === "create-file") {
5581
- const abs2 = join18(repoRoot, op.path);
5582
- const exists = existsSync16(abs2);
5731
+ const abs2 = join19(repoRoot, op.path);
5732
+ const exists = existsSync17(abs2);
5583
5733
  const owned = ownedFiles.has(op.path);
5584
5734
  let action;
5585
5735
  if (op.path === CONFIG_PATH || op.path === DOC_OWNERS_PATH) {
@@ -5589,7 +5739,7 @@ function planUpgrade(ops, repoRoot, manifest, routing) {
5589
5739
  } else if (!exists) {
5590
5740
  action = "recreate";
5591
5741
  hasChanges = true;
5592
- } else if (readFileSync16(abs2, "utf-8") !== op.content) {
5742
+ } else if (readFileSync17(abs2, "utf-8") !== op.content) {
5593
5743
  action = "regenerate";
5594
5744
  hasChanges = true;
5595
5745
  } else {
@@ -5602,11 +5752,11 @@ function planUpgrade(ops, repoRoot, manifest, routing) {
5602
5752
  let action;
5603
5753
  if (!owned) {
5604
5754
  action = "not-installed";
5605
- } else if (!existsSync16(abs2)) {
5755
+ } else if (!existsSync17(abs2)) {
5606
5756
  action = "recreate-host";
5607
5757
  hasChanges = true;
5608
5758
  } else {
5609
- const content = readFileSync16(abs2, "utf-8");
5759
+ const content = readFileSync17(abs2, "utf-8");
5610
5760
  const { begin, end } = markerLines(op.marker, op.comment);
5611
5761
  if (!(content.includes(begin) && content.includes(end))) {
5612
5762
  action = "recreate-append";
@@ -5706,7 +5856,7 @@ function renderUpgradeDiff(plan) {
5706
5856
  }
5707
5857
  function regenerateBlock(repoRoot, op) {
5708
5858
  const abs2 = resolveManagedBlockPath(repoRoot, op.path);
5709
- const content = readFileSync16(abs2, "utf-8");
5859
+ const content = readFileSync17(abs2, "utf-8");
5710
5860
  const stripped = stripBlockFromContent(content, op.marker, op.comment);
5711
5861
  if (stripped !== null) {
5712
5862
  writeFileSync9(abs2, stripped.endsWith(`
@@ -5719,7 +5869,7 @@ function applyUpgrade(plan, repoRoot) {
5719
5869
  for (const e of plan.entries) {
5720
5870
  if (e.kind === "create-file") {
5721
5871
  if (e.action === "regenerate" || e.action === "recreate") {
5722
- writeFileWithDirs(join18(repoRoot, e.op.path), e.op.content, e.op.mode);
5872
+ writeFileWithDirs(join19(repoRoot, e.op.path), e.op.content, e.op.mode);
5723
5873
  }
5724
5874
  } else {
5725
5875
  if (e.action === "recreate-host")
@@ -5734,9 +5884,9 @@ function applyUpgrade(plan, repoRoot) {
5734
5884
  if (!s.present)
5735
5885
  continue;
5736
5886
  const abs2 = resolveManagedBlockPath(repoRoot, s.path);
5737
- if (!existsSync16(abs2))
5887
+ if (!existsSync17(abs2))
5738
5888
  continue;
5739
- const stripped = stripBlockFromContent(readFileSync16(abs2, "utf-8"), s.marker, s.comment);
5889
+ const stripped = stripBlockFromContent(readFileSync17(abs2, "utf-8"), s.marker, s.comment);
5740
5890
  if (stripped === null)
5741
5891
  continue;
5742
5892
  if (blockStripLeavesEmpty(stripped))
@@ -6167,6 +6317,15 @@ var COMMANDS = [
6167
6317
  ],
6168
6318
  status: "shipped"
6169
6319
  },
6320
+ {
6321
+ name: "studio",
6322
+ description: "Launch Vinaya Studio — runs the Studio dev app when its source (apps/vinaya/web) is in a checkout above the current directory; published installs refuse with a clear message, since no published build bundles the Studio app yet",
6323
+ details: [
6324
+ "Resolution happens in this order: a workspace checkout carrying `apps/vinaya/web` (Studio's source, which lives in the attalabs monorepo — not this repository) runs the dev app directly; an install whose package root carries a `studio-standalone/` bundle would run that bundled server; anything else gets an explicit refusal and exit 1 rather than a silent no-op.",
6325
+ "No published `@attalabs/vinaya` build ships the `studio-standalone/` bundle today — producing and shipping it is the Studio-packaging work, tracked separately. Until it lands, this command is honest about the shapes it cannot serve instead of pretending to serve them."
6326
+ ],
6327
+ status: "shipped"
6328
+ },
6170
6329
  {
6171
6330
  name: "quickstart",
6172
6331
  description: "Guided wizard: init, doc-owners, project, commit, demo break, doctor, push — one command",
@@ -6206,9 +6365,9 @@ function printHelp() {
6206
6365
  }
6207
6366
 
6208
6367
  // src/index.ts
6209
- var PACKAGE_ROOT = join19(dirname7(fileURLToPath2(import.meta.url)), "..");
6368
+ var PACKAGE_ROOT = join20(dirname8(fileURLToPath2(import.meta.url)), "..");
6210
6369
  function readVersion2() {
6211
- const pkg = JSON.parse(readFileSync17(join19(PACKAGE_ROOT, "package.json"), "utf-8"));
6370
+ const pkg = JSON.parse(readFileSync18(join20(PACKAGE_ROOT, "package.json"), "utf-8"));
6212
6371
  return pkg.version;
6213
6372
  }
6214
6373
  var [, , command, ...args] = process.argv;
@@ -6228,6 +6387,11 @@ try {
6228
6387
  }
6229
6388
  break;
6230
6389
  }
6390
+ case "studio": {
6391
+ const code = await runStudio(process.cwd(), args);
6392
+ process.exit(code);
6393
+ break;
6394
+ }
6231
6395
  case "init": {
6232
6396
  const [subcommand, ...rest] = args;
6233
6397
  if (subcommand === "product") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attalabs/vinaya",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "description": "Vinaya — Agentic Engineering Harness. Deterministic checks every AI coding agent must satisfy before merge.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -38,6 +38,7 @@
38
38
  "scripts": {
39
39
  "build": "bun scripts/build.ts",
40
40
  "bundle-doctrine": "bun scripts/bundle-doctrine.ts",
41
+ "bundle-studio": "bun scripts/bundle-studio.ts",
41
42
  "prepack": "bun scripts/build.ts && bun scripts/bundle-doctrine.ts",
42
43
  "test": "bun test",
43
44
  "typecheck": "tsc --noEmit",
@@ -49,9 +50,10 @@
49
50
  "zod": "3.25.76"
50
51
  },
51
52
  "devDependencies": {
52
- "@atta/aeg-core": "workspace:*",
53
+ "@attalabs/aeg-core": "workspace:*",
54
+ "@attalabs/aeg-forge-state": "workspace:*",
53
55
  "@atta/typescript-config": "workspace:*",
54
- "@atta/vinaya-sources": "workspace:*",
56
+ "@attalabs/vinaya-sources": "workspace:*",
55
57
  "@types/bun": "latest",
56
58
  "typescript": "^5.5.0"
57
59
  }