@a5c-ai/git-a5c 1.0.2

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.
Files changed (99) hide show
  1. package/README.md +32 -0
  2. package/dist/args.d.ts +39 -0
  3. package/dist/args.d.ts.map +1 -0
  4. package/dist/args.js +79 -0
  5. package/dist/args.js.map +1 -0
  6. package/dist/bin/git-a5c.d.ts +3 -0
  7. package/dist/bin/git-a5c.d.ts.map +1 -0
  8. package/dist/bin/git-a5c.js +12 -0
  9. package/dist/bin/git-a5c.js.map +1 -0
  10. package/dist/commands/agent.d.ts +3 -0
  11. package/dist/commands/agent.d.ts.map +1 -0
  12. package/dist/commands/agent.js +61 -0
  13. package/dist/commands/agent.js.map +1 -0
  14. package/dist/commands/block.d.ts +3 -0
  15. package/dist/commands/block.d.ts.map +1 -0
  16. package/dist/commands/block.js +33 -0
  17. package/dist/commands/block.js.map +1 -0
  18. package/dist/commands/gate.d.ts +3 -0
  19. package/dist/commands/gate.d.ts.map +1 -0
  20. package/dist/commands/gate.js +31 -0
  21. package/dist/commands/gate.js.map +1 -0
  22. package/dist/commands/help.d.ts +3 -0
  23. package/dist/commands/help.d.ts.map +1 -0
  24. package/dist/commands/help.js +35 -0
  25. package/dist/commands/help.js.map +1 -0
  26. package/dist/commands/hooks.d.ts +3 -0
  27. package/dist/commands/hooks.d.ts.map +1 -0
  28. package/dist/commands/hooks.js +40 -0
  29. package/dist/commands/hooks.js.map +1 -0
  30. package/dist/commands/issue.d.ts +3 -0
  31. package/dist/commands/issue.d.ts.map +1 -0
  32. package/dist/commands/issue.js +134 -0
  33. package/dist/commands/issue.js.map +1 -0
  34. package/dist/commands/journal.d.ts +3 -0
  35. package/dist/commands/journal.d.ts.map +1 -0
  36. package/dist/commands/journal.js +78 -0
  37. package/dist/commands/journal.js.map +1 -0
  38. package/dist/commands/ops.d.ts +3 -0
  39. package/dist/commands/ops.d.ts.map +1 -0
  40. package/dist/commands/ops.js +39 -0
  41. package/dist/commands/ops.js.map +1 -0
  42. package/dist/commands/pr.d.ts +3 -0
  43. package/dist/commands/pr.d.ts.map +1 -0
  44. package/dist/commands/pr.js +136 -0
  45. package/dist/commands/pr.js.map +1 -0
  46. package/dist/commands/status.d.ts +3 -0
  47. package/dist/commands/status.d.ts.map +1 -0
  48. package/dist/commands/status.js +17 -0
  49. package/dist/commands/status.js.map +1 -0
  50. package/dist/commands/types.d.ts +17 -0
  51. package/dist/commands/types.d.ts.map +1 -0
  52. package/dist/commands/types.js +2 -0
  53. package/dist/commands/types.js.map +1 -0
  54. package/dist/commands/verify.d.ts +3 -0
  55. package/dist/commands/verify.d.ts.map +1 -0
  56. package/dist/commands/verify.js +20 -0
  57. package/dist/commands/verify.js.map +1 -0
  58. package/dist/commands/webhook.d.ts +3 -0
  59. package/dist/commands/webhook.d.ts.map +1 -0
  60. package/dist/commands/webhook.js +61 -0
  61. package/dist/commands/webhook.js.map +1 -0
  62. package/dist/git.d.ts +5 -0
  63. package/dist/git.d.ts.map +1 -0
  64. package/dist/git.js +35 -0
  65. package/dist/git.js.map +1 -0
  66. package/dist/run.d.ts +7 -0
  67. package/dist/run.d.ts.map +1 -0
  68. package/dist/run.js +83 -0
  69. package/dist/run.js.map +1 -0
  70. package/dist/time.d.ts +2 -0
  71. package/dist/time.d.ts.map +1 -0
  72. package/dist/time.js +16 -0
  73. package/dist/time.js.map +1 -0
  74. package/package.json +24 -0
  75. package/src/args.ts +87 -0
  76. package/src/bin/git-a5c.ts +14 -0
  77. package/src/commands/agent.ts +72 -0
  78. package/src/commands/block.ts +34 -0
  79. package/src/commands/gate.ts +32 -0
  80. package/src/commands/help.ts +38 -0
  81. package/src/commands/hooks.ts +40 -0
  82. package/src/commands/issue.ts +146 -0
  83. package/src/commands/journal.ts +75 -0
  84. package/src/commands/ops.ts +41 -0
  85. package/src/commands/pr.ts +147 -0
  86. package/src/commands/status.ts +18 -0
  87. package/src/commands/types.ts +20 -0
  88. package/src/commands/verify.ts +20 -0
  89. package/src/commands/webhook.ts +63 -0
  90. package/src/git.ts +38 -0
  91. package/src/run.ts +99 -0
  92. package/src/time.ts +16 -0
  93. package/test/_util.ts +88 -0
  94. package/test/cli.flow.integration.test.ts +86 -0
  95. package/test/cli.snapshots.test.ts +50 -0
  96. package/test/cli.webhook.integration.test.ts +62 -0
  97. package/test/cli.write.integration.test.ts +70 -0
  98. package/tsconfig.json +13 -0
  99. package/vitest.config.ts +20 -0
package/dist/run.js ADDED
@@ -0,0 +1,83 @@
1
+ import { detectRepoRoot } from "./git.js";
2
+ import { parseArgs } from "./args.js";
3
+ import { createLogger, loadSnapshot, openRepo, parseLogLevel } from "@a5cforge/sdk";
4
+ import { handleHelp } from "./commands/help.js";
5
+ import { handleWebhook } from "./commands/webhook.js";
6
+ import { handleStatus } from "./commands/status.js";
7
+ import { handleIssue } from "./commands/issue.js";
8
+ import { handlePr } from "./commands/pr.js";
9
+ import { handleBlock } from "./commands/block.js";
10
+ import { handleGate } from "./commands/gate.js";
11
+ import { handleAgent } from "./commands/agent.js";
12
+ import { handleOps } from "./commands/ops.js";
13
+ import { handleHooks } from "./commands/hooks.js";
14
+ import { handleVerify } from "./commands/verify.js";
15
+ import { handleJournal } from "./commands/journal.js";
16
+ function writeLine(write, s = "") {
17
+ write(s + "\n");
18
+ }
19
+ function nowMs() {
20
+ // Test hook for deterministic journal/active: set A5C_NOW_ISO=...
21
+ const iso = process.env.A5C_NOW_ISO;
22
+ if (iso) {
23
+ const ms = Date.parse(iso);
24
+ if (Number.isFinite(ms))
25
+ return ms;
26
+ }
27
+ return Date.now();
28
+ }
29
+ export async function runCli(argv, opts = {}) {
30
+ const cwd = opts.cwd ?? process.cwd();
31
+ const out = opts.stdout ?? ((s) => process.stdout.write(s));
32
+ const err = opts.stderr ?? ((s) => process.stderr.write(s));
33
+ const log = createLogger({ base: { component: "cli" }, level: parseLogLevel(process.env.A5C_LOG_LEVEL ?? "silent") });
34
+ const { flags, positionals } = parseArgs(argv);
35
+ const cmd = positionals[0] ?? "help";
36
+ let repoRoot;
37
+ try {
38
+ repoRoot = typeof flags.repo === "string" ? flags.repo : await detectRepoRoot(cwd);
39
+ }
40
+ catch {
41
+ writeLine(err, "not a git repository (use --repo <path>)");
42
+ return 2;
43
+ }
44
+ const treeish = typeof flags.treeish === "string" ? flags.treeish : "HEAD";
45
+ log.debug("start", { cmd, treeish, repoRoot });
46
+ const repo = await openRepo(repoRoot);
47
+ const snap = await loadSnapshot({ git: repo.git, treeish, inboxRefs: flags.inboxRefs });
48
+ const baseArgs = {
49
+ repoRoot,
50
+ treeish,
51
+ flags,
52
+ positionals,
53
+ repo,
54
+ snap,
55
+ nowMs,
56
+ io: { out, err, writeLine }
57
+ };
58
+ const handlers = [
59
+ () => handleHelp(baseArgs),
60
+ () => handleWebhook(baseArgs),
61
+ () => handleStatus(baseArgs),
62
+ () => handleIssue(baseArgs),
63
+ () => handlePr(baseArgs),
64
+ () => handleBlock(baseArgs),
65
+ () => handleGate(baseArgs),
66
+ () => handleAgent(baseArgs),
67
+ () => handleOps(baseArgs),
68
+ () => handleHooks(baseArgs),
69
+ () => handleVerify(baseArgs),
70
+ () => handleJournal(baseArgs)
71
+ ];
72
+ for (const h of handlers) {
73
+ const r = await h();
74
+ if (r !== undefined) {
75
+ log.debug("done", { code: r });
76
+ return r;
77
+ }
78
+ }
79
+ writeLine(err, `unknown command: ${cmd}`);
80
+ log.warn("unknown_command", { cmd });
81
+ return 2;
82
+ }
83
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEpF,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAQtD,SAAS,SAAS,CAAC,KAA0B,EAAE,CAAC,GAAG,EAAE;IACnD,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,KAAK;IACZ,kEAAkE;IAClE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IACpC,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YAAE,OAAO,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAc,EAAE,OAAmB,EAAE;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEtH,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/C,MAAM,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IAErC,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,SAAS,CAAC,GAAG,EAAE,0CAA0C,CAAC,CAAC;QAC3D,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IAE3E,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;IAExF,MAAM,QAAQ,GAAgB;QAC5B,QAAQ;QACR,OAAO;QACP,KAAK;QACL,WAAW;QACX,IAAI;QACJ,IAAI;QACJ,KAAK;QACL,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE;KAC5B,CAAC;IAEF,MAAM,QAAQ,GAAkE;QAC9E,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAC1B,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC;QAC7B,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC;QAC5B,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC;QAC3B,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACxB,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC;QAC3B,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAC1B,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC;QAC3B,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC;QACzB,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC;QAC3B,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC;QAC5B,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC;KAC9B,CAAC;IAEF,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;YAC/B,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAG,EAAE,oBAAoB,GAAG,EAAE,CAAC,CAAC;IAC1C,GAAG,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IACrC,OAAO,CAAC,CAAC;AACX,CAAC"}
package/dist/time.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function parseSinceToEpochMs(since: string, nowMs: number): number;
2
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAAA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAaxE"}
package/dist/time.js ADDED
@@ -0,0 +1,16 @@
1
+ export function parseSinceToEpochMs(since, nowMs) {
2
+ // Accept:
3
+ // - ISO timestamp
4
+ // - durations like 2h, 15m, 30s, 7d
5
+ const iso = Date.parse(since);
6
+ if (Number.isFinite(iso))
7
+ return iso;
8
+ const m = /^(\d+)(s|m|h|d)$/.exec(since.trim());
9
+ if (!m)
10
+ throw new Error(`Invalid --since: ${since}`);
11
+ const n = Number(m[1]);
12
+ const unit = m[2];
13
+ const mult = unit === "s" ? 1000 : unit === "m" ? 60_000 : unit === "h" ? 3_600_000 : 86_400_000;
14
+ return nowMs - n * mult;
15
+ }
16
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../src/time.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,mBAAmB,CAAC,KAAa,EAAE,KAAa;IAC9D,UAAU;IACV,kBAAkB;IAClB,oCAAoC;IACpC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IAErC,MAAM,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAChD,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,KAAK,EAAE,CAAC,CAAC;IACrD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAClB,MAAM,IAAI,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;IACjG,OAAO,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC;AAC1B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@a5c-ai/git-a5c",
3
+ "version": "1.0.2",
4
+ "type": "module",
5
+ "bin": {
6
+ "git-a5c": "./dist/bin/git-a5c.js"
7
+ },
8
+ "dependencies": {
9
+ "@a5cforge/sdk": "0.1.0"
10
+ },
11
+ "devDependencies": {
12
+ "@vitest/coverage-v8": "^2.1.8",
13
+ "typescript": "^5.7.3",
14
+ "vitest": "^2.1.8"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "test": "vitest run",
22
+ "test:coverage": "vitest run --coverage"
23
+ }
24
+ }
package/src/args.ts ADDED
@@ -0,0 +1,87 @@
1
+ export type ParsedArgs = {
2
+ flags: {
3
+ repo?: string;
4
+ treeish?: string;
5
+ json?: boolean;
6
+ inboxRefs?: string[];
7
+ since?: string;
8
+ limit?: number;
9
+ types?: string[];
10
+ entity?: string;
11
+ active?: boolean;
12
+ stageOnly?: boolean;
13
+ commit?: boolean;
14
+ message?: string;
15
+ title?: string;
16
+ body?: string;
17
+ id?: string;
18
+ commentId?: string;
19
+ reason?: string;
20
+ base?: string;
21
+ head?: string;
22
+ headRef?: string;
23
+ topic?: string;
24
+ by?: string;
25
+ op?: string;
26
+ agentId?: string;
27
+ ttlSeconds?: number;
28
+ task?: string;
29
+ env?: string;
30
+ rev?: string;
31
+ artifact?: string;
32
+ dispatchId?: string;
33
+ url?: string;
34
+ type?: string;
35
+ };
36
+ positionals: string[];
37
+ };
38
+
39
+ export function parseArgs(argv: string[]): ParsedArgs {
40
+ const args = [...argv];
41
+ const flags: ParsedArgs["flags"] = {};
42
+ const positionals: string[] = [];
43
+
44
+ while (args.length) {
45
+ const a = args.shift()!;
46
+ if (a === "--repo") flags.repo = args.shift()!;
47
+ else if (a === "--treeish") flags.treeish = args.shift()!;
48
+ else if (a === "--json") flags.json = true;
49
+ else if (a === "--inbox-ref") {
50
+ const v = args.shift()!;
51
+ flags.inboxRefs ??= [];
52
+ flags.inboxRefs.push(v);
53
+ } else if (a === "--since") flags.since = args.shift()!;
54
+ else if (a === "--limit") flags.limit = Number(args.shift()!);
55
+ else if (a === "--types") flags.types = args.shift()!.split(",").map((s) => s.trim()).filter(Boolean);
56
+ else if (a === "--entity") flags.entity = args.shift()!;
57
+ else if (a === "--active") flags.active = true;
58
+ else if (a === "--stage-only") flags.stageOnly = true;
59
+ else if (a === "--commit") flags.commit = true;
60
+ else if (a === "--message" || a === "-m") flags.message = args.shift()!;
61
+ else if (a === "--title") flags.title = args.shift()!;
62
+ else if (a === "--body") flags.body = args.shift()!;
63
+ else if (a === "--id") flags.id = args.shift()!;
64
+ else if (a === "--comment-id") flags.commentId = args.shift()!;
65
+ else if (a === "--reason") flags.reason = args.shift()!;
66
+ else if (a === "--base") flags.base = args.shift()!;
67
+ else if (a === "--head") flags.head = args.shift()!;
68
+ else if (a === "--head-ref") flags.headRef = args.shift()!;
69
+ else if (a === "--topic") flags.topic = args.shift()!;
70
+ else if (a === "--by") flags.by = args.shift()!;
71
+ else if (a === "--op") flags.op = args.shift()!;
72
+ else if (a === "--agent-id") flags.agentId = args.shift()!;
73
+ else if (a === "--ttl-seconds") flags.ttlSeconds = Number(args.shift()!);
74
+ else if (a === "--task") flags.task = args.shift()!;
75
+ else if (a === "--env") flags.env = args.shift()!;
76
+ else if (a === "--rev") flags.rev = args.shift()!;
77
+ else if (a === "--artifact") flags.artifact = args.shift()!;
78
+ else if (a === "--dispatch-id") flags.dispatchId = args.shift()!;
79
+ else if (a === "--url") flags.url = args.shift()!;
80
+ else if (a === "--type") flags.type = args.shift()!;
81
+ else positionals.push(a);
82
+ }
83
+
84
+ return { flags, positionals };
85
+ }
86
+
87
+
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../run.js";
3
+
4
+ runCli(process.argv.slice(2))
5
+ .then((code) => {
6
+ process.exitCode = code;
7
+ })
8
+ .catch((e) => {
9
+ // Keep output minimal and deterministic.
10
+ process.stderr.write(String(e?.message ?? e) + "\n");
11
+ process.exitCode = 1;
12
+ });
13
+
14
+
@@ -0,0 +1,72 @@
1
+ import type { CommandArgs } from "./types.js";
2
+ import { git, gitConfigGet } from "../git.js";
3
+ import {
4
+ HlcClock,
5
+ UlidGenerator,
6
+ loadHlcState,
7
+ saveHlcState,
8
+ stageFiles,
9
+ writeAgentHeartbeat,
10
+ writeAgentDispatchCreated
11
+ } from "@a5cforge/sdk";
12
+
13
+ export async function handleAgent(args: CommandArgs): Promise<number | undefined> {
14
+ if (args.positionals[0] !== "agent") return;
15
+ const sub = args.positionals[1];
16
+ const actor = process.env.A5C_ACTOR ?? (await gitConfigGet(args.repoRoot, "user.name")) ?? "unknown";
17
+ const persisted = (await loadHlcState(actor)) ?? { wallMs: 0, counter: 0 };
18
+ const clock = new HlcClock(persisted);
19
+ let nonce = 0;
20
+ const ctx = { repoRoot: args.repoRoot, actor, clock, nextNonce: () => String(++nonce).padStart(4, "0") };
21
+
22
+ if (sub === "heartbeat") {
23
+ const agentId = args.flags.agentId ?? actor;
24
+ const ttlSeconds = args.flags.ttlSeconds ?? 120;
25
+ const time = new Date(args.nowMs()).toISOString();
26
+ const status = args.flags.message ?? undefined;
27
+ const entityId = args.flags.entity;
28
+ const entity = entityId ? ({ type: entityId.startsWith("pr-") ? "pr" : "issue", id: entityId } as const) : undefined;
29
+ const res = await writeAgentHeartbeat(ctx, { agentId, ttlSeconds, status, entity, time });
30
+ await saveHlcState(actor, clock.now());
31
+ if (args.flags.stageOnly || args.flags.commit) await stageFiles(args.repoRoot, [res.path]);
32
+ if (args.flags.commit) {
33
+ const msg = args.flags.message ?? `a5c: agent heartbeat ${agentId}`;
34
+ await git(["-c", "user.name=a5c", "-c", "user.email=a5c@example.invalid", "commit", "-m", msg], args.repoRoot);
35
+ }
36
+ args.io.writeLine(args.io.out, res.path);
37
+ return 0;
38
+ }
39
+
40
+ if (sub === "dispatch") {
41
+ const entityId = args.flags.entity;
42
+ if (!entityId) {
43
+ args.io.writeLine(args.io.err, "usage: git a5c agent dispatch --entity <issueId|prKey> [--dispatch-id ...] [--task ...]");
44
+ return 2;
45
+ }
46
+ const dispatchId = args.flags.dispatchId ?? `d-${new UlidGenerator().generate()}`;
47
+ const time = new Date(args.nowMs()).toISOString();
48
+ const entity = { type: entityId.startsWith("pr-") ? "pr" : "issue", id: entityId } as const;
49
+ const agentId = args.flags.agentId ?? actor;
50
+ const res = await writeAgentDispatchCreated(ctx, {
51
+ dispatchId,
52
+ agentId,
53
+ entity,
54
+ task: args.flags.task,
55
+ params: undefined,
56
+ time
57
+ });
58
+ await saveHlcState(actor, clock.now());
59
+ if (args.flags.stageOnly || args.flags.commit) await stageFiles(args.repoRoot, [res.path]);
60
+ if (args.flags.commit) {
61
+ const msg = args.flags.message ?? `a5c: agent dispatch ${dispatchId}`;
62
+ await git(["-c", "user.name=a5c", "-c", "user.email=a5c@example.invalid", "commit", "-m", msg], args.repoRoot);
63
+ }
64
+ args.io.writeLine(args.io.out, res.path);
65
+ return 0;
66
+ }
67
+
68
+ args.io.writeLine(args.io.err, "usage: git a5c agent heartbeat|dispatch ...");
69
+ return 2;
70
+ }
71
+
72
+
@@ -0,0 +1,34 @@
1
+ import type { CommandArgs } from "./types.js";
2
+ import { git, gitConfigGet } from "../git.js";
3
+ import { HlcClock, loadHlcState, saveHlcState, stageFiles, writeDepChanged } from "@a5cforge/sdk";
4
+
5
+ export async function handleBlock(args: CommandArgs): Promise<number | undefined> {
6
+ if (args.positionals[0] !== "block") return;
7
+ // git a5c block <entityId> --by <issueOrPrId> [--op add|remove]
8
+ const entityId = args.positionals[1];
9
+ const byId = args.flags.by;
10
+ const op = (args.flags.op as any) ?? "add";
11
+ if (!entityId || !byId) {
12
+ args.io.writeLine(args.io.err, "usage: git a5c block <entityId> --by <issue|pr> [--op add|remove]");
13
+ return 2;
14
+ }
15
+ const entity = { type: entityId.startsWith("pr-") ? "pr" : "issue", id: entityId } as const;
16
+ const by = { type: byId.startsWith("pr-") ? "pr" : "issue", id: byId } as const;
17
+ const actor = process.env.A5C_ACTOR ?? (await gitConfigGet(args.repoRoot, "user.name")) ?? "unknown";
18
+ const time = new Date(args.nowMs()).toISOString();
19
+ const persisted = (await loadHlcState(actor)) ?? { wallMs: 0, counter: 0 };
20
+ const clock = new HlcClock(persisted);
21
+ let nonce = 0;
22
+ const ctx = { repoRoot: args.repoRoot, actor, clock, nextNonce: () => String(++nonce).padStart(4, "0") };
23
+ const res = await writeDepChanged(ctx, { entity, op, by, note: args.flags.message as any, time });
24
+ await saveHlcState(actor, clock.now());
25
+ if (args.flags.stageOnly || args.flags.commit) await stageFiles(args.repoRoot, [res.path]);
26
+ if (args.flags.commit) {
27
+ const msg = args.flags.message ?? `a5c: dep ${op} ${entityId}`;
28
+ await git(["-c", "user.name=a5c", "-c", "user.email=a5c@example.invalid", "commit", "-m", msg], args.repoRoot);
29
+ }
30
+ args.io.writeLine(args.io.out, res.path);
31
+ return 0;
32
+ }
33
+
34
+
@@ -0,0 +1,32 @@
1
+ import type { CommandArgs } from "./types.js";
2
+ import { git, gitConfigGet } from "../git.js";
3
+ import { HlcClock, loadHlcState, saveHlcState, stageFiles, writeGateChanged } from "@a5cforge/sdk";
4
+
5
+ export async function handleGate(args: CommandArgs): Promise<number | undefined> {
6
+ if (args.positionals[0] !== "gate") return;
7
+ const sub = args.positionals[1];
8
+ const entityId = args.positionals[2];
9
+ if (!sub || !entityId) {
10
+ args.io.writeLine(args.io.err, "usage: git a5c gate needs-human|clear <entityId> [--topic t] [-m msg]");
11
+ return 2;
12
+ }
13
+ const entity = { type: entityId.startsWith("pr-") ? "pr" : "issue", id: entityId } as const;
14
+ const needsHuman = sub === "needs-human";
15
+ const actor = process.env.A5C_ACTOR ?? (await gitConfigGet(args.repoRoot, "user.name")) ?? "unknown";
16
+ const time = new Date(args.nowMs()).toISOString();
17
+ const persisted = (await loadHlcState(actor)) ?? { wallMs: 0, counter: 0 };
18
+ const clock = new HlcClock(persisted);
19
+ let nonce = 0;
20
+ const ctx = { repoRoot: args.repoRoot, actor, clock, nextNonce: () => String(++nonce).padStart(4, "0") };
21
+ const res = await writeGateChanged(ctx, { entity, needsHuman, topic: args.flags.topic, message: args.flags.message as any, time });
22
+ await saveHlcState(actor, clock.now());
23
+ if (args.flags.stageOnly || args.flags.commit) await stageFiles(args.repoRoot, [res.path]);
24
+ if (args.flags.commit) {
25
+ const msg = args.flags.message ?? `a5c: gate ${sub} ${entityId}`;
26
+ await git(["-c", "user.name=a5c", "-c", "user.email=a5c@example.invalid", "commit", "-m", msg], args.repoRoot);
27
+ }
28
+ args.io.writeLine(args.io.out, res.path);
29
+ return 0;
30
+ }
31
+
32
+
@@ -0,0 +1,38 @@
1
+ import type { CommandArgs } from "./types.js";
2
+
3
+ export function handleHelp(args: CommandArgs): number | undefined {
4
+ const cmd = args.positionals[0] ?? "help";
5
+ if (cmd !== "help" && cmd !== "--help" && cmd !== "-h") return;
6
+
7
+ const { writeLine } = args.io;
8
+ const out = args.io.out;
9
+ writeLine(out, "git a5c <command> [--json] [--treeish <ref>] [--repo <path>] [--inbox-ref <ref>...]");
10
+ writeLine(out, "");
11
+ writeLine(out, "Commands:");
12
+ writeLine(out, " status");
13
+ writeLine(out, " issue list");
14
+ writeLine(out, " issue show <id>");
15
+ writeLine(out, " issue new --title <t> [--body <b>] [--stage-only|--commit]");
16
+ writeLine(out, " issue comment <id> -m <text> [--comment-id <id>] [--stage-only|--commit]");
17
+ writeLine(out, " issue edit-comment <commentId> --id <issueId> -m <text> [--stage-only|--commit]");
18
+ writeLine(out, " issue redact-comment <commentId> --id <issueId> [--reason <r>] [--stage-only|--commit]");
19
+ writeLine(out, " pr list");
20
+ writeLine(out, " pr show <prKey>");
21
+ writeLine(out, " pr propose --base <ref> --head <ref> --title <t> [--body <b>] [--stage-only|--commit]");
22
+ writeLine(out, " pr request --base <ref> --title <t> [--body <b>] [--stage-only|--commit]");
23
+ writeLine(out, " pr claim <prKey> --head-ref <ref> [-m <msg>] [--stage-only|--commit]");
24
+ writeLine(out, " pr bind-head <prKey> --head-ref <ref> [-m <msg>] [--stage-only|--commit]");
25
+ writeLine(out, " block <entityId> --by <issue|pr> [--op add|remove] [-m <note>] [--stage-only|--commit]");
26
+ writeLine(out, " gate needs-human <entityId> [--topic <t>] [-m <msg>] [--stage-only|--commit]");
27
+ writeLine(out, " gate clear <entityId> [-m <msg>] [--stage-only|--commit]");
28
+ writeLine(out, " agent heartbeat [--agent-id <id>] [--ttl-seconds N] [--entity <id>] [-m <status>] [--stage-only|--commit]");
29
+ writeLine(out, " ops deploy --entity <id> [--artifact <uri>] [-m <status>] [--stage-only|--commit]");
30
+ writeLine(out, " verify");
31
+ writeLine(out, " journal [--since <2h|2025-...>] [--limit N] [--types a,b] [--entity <id>] [--active]");
32
+ writeLine(out, " hooks install|uninstall");
33
+ writeLine(out, " webhook status");
34
+ writeLine(out, " webhook test --url <url> [--type <type>]");
35
+ return 0;
36
+ }
37
+
38
+
@@ -0,0 +1,40 @@
1
+ import type { CommandArgs } from "./types.js";
2
+ import fs from "node:fs/promises";
3
+ import { gitPath } from "../git.js";
4
+
5
+ export async function handleHooks(args: CommandArgs): Promise<number | undefined> {
6
+ if (args.positionals[0] !== "hooks") return;
7
+ const sub = args.positionals[1];
8
+ if (sub !== "install" && sub !== "uninstall") {
9
+ args.io.writeLine(args.io.err, "usage: git a5c hooks install|uninstall");
10
+ return 2;
11
+ }
12
+ const hooksDir = await gitPath(args.repoRoot, "hooks");
13
+ const hookFiles = ["post-commit", "post-merge"];
14
+ if (sub === "uninstall") {
15
+ for (const f of hookFiles) {
16
+ try {
17
+ const p = `${hooksDir}/${f}`;
18
+ const cur = await fs.readFile(p, "utf8");
19
+ if (cur.includes("A5C-HOOK-MANAGED: yes")) {
20
+ await fs.unlink(p);
21
+ }
22
+ } catch {}
23
+ }
24
+ args.io.writeLine(args.io.out, "ok");
25
+ return 0;
26
+ }
27
+ const script = `#!/bin/sh\n# a5cforge hook (generated)\n# A5C-HOOK-MANAGED: yes\n# Keep it quiet; write last journal to .git\nif command -v git >/dev/null 2>&1; then\n git a5c journal --since 2h --limit 20 --json > "$(git rev-parse --git-path a5c-last-journal.json)" 2>/dev/null || true\nfi\nexit 0\n`;
28
+ await fs.mkdir(hooksDir, { recursive: true });
29
+ for (const f of hookFiles) {
30
+ const p = `${hooksDir}/${f}`;
31
+ await fs.writeFile(p, script, "utf8");
32
+ try {
33
+ await fs.chmod(p, 0o755);
34
+ } catch {}
35
+ }
36
+ args.io.writeLine(args.io.out, "ok");
37
+ return 0;
38
+ }
39
+
40
+
@@ -0,0 +1,146 @@
1
+ import type { CommandArgs } from "./types.js";
2
+ import { git, gitConfigGet } from "../git.js";
3
+ import {
4
+ HlcClock,
5
+ UlidGenerator,
6
+ loadHlcState,
7
+ saveHlcState,
8
+ stageFiles,
9
+ listIssues,
10
+ renderIssue,
11
+ writeIssueCreated,
12
+ writeCommentCreated,
13
+ writeCommentEdited,
14
+ writeCommentRedacted
15
+ } from "@a5cforge/sdk";
16
+
17
+ export async function handleIssue(args: CommandArgs): Promise<number | undefined> {
18
+ if (args.positionals[0] !== "issue") return;
19
+ const sub = args.positionals[1];
20
+
21
+ if (sub === "list") {
22
+ const ids = listIssues(args.snap);
23
+ if (args.flags.json) args.io.writeLine(args.io.out, JSON.stringify(ids, null, 2));
24
+ else ids.forEach((id) => args.io.writeLine(args.io.out, id));
25
+ return 0;
26
+ }
27
+
28
+ if (sub === "show") {
29
+ const id = args.positionals[2];
30
+ if (!id) throw new Error("missing issue id");
31
+ const issue = renderIssue(args.snap, id);
32
+ if (!issue) {
33
+ args.io.writeLine(args.io.err, `not found: ${id}`);
34
+ return 2;
35
+ }
36
+ if (args.flags.json) args.io.writeLine(args.io.out, JSON.stringify(issue, null, 2));
37
+ else {
38
+ args.io.writeLine(args.io.out, `${issue.issueId}: ${issue.title}`);
39
+ if (issue.body) args.io.writeLine(args.io.out, issue.body);
40
+ args.io.writeLine(args.io.out, `comments: ${issue.comments.length}`);
41
+ }
42
+ return 0;
43
+ }
44
+
45
+ if (sub === "new") {
46
+ const title = args.flags.title;
47
+ if (!title) {
48
+ args.io.writeLine(args.io.err, "missing --title");
49
+ return 2;
50
+ }
51
+ const actor = process.env.A5C_ACTOR ?? (await gitConfigGet(args.repoRoot, "user.name")) ?? "unknown";
52
+ const issueId = args.flags.id ?? `issue-${new UlidGenerator().generate()}`;
53
+ const time = new Date(args.nowMs()).toISOString();
54
+ const persisted = (await loadHlcState(actor)) ?? { wallMs: 0, counter: 0 };
55
+ const clock = new HlcClock(persisted);
56
+ let nonce = 0;
57
+ const ctx = { repoRoot: args.repoRoot, actor, clock, nextNonce: () => String(++nonce).padStart(4, "0") };
58
+ const res = await writeIssueCreated(ctx, { issueId, title, body: args.flags.body, time });
59
+ await saveHlcState(actor, clock.now());
60
+ if (args.flags.stageOnly || args.flags.commit) await stageFiles(args.repoRoot, [res.path]);
61
+ if (args.flags.commit) {
62
+ const msg = args.flags.message ?? `a5c: issue new ${issueId}`;
63
+ await git(["-c", "user.name=a5c", "-c", "user.email=a5c@example.invalid", "commit", "-m", msg], args.repoRoot);
64
+ }
65
+ args.io.writeLine(args.io.out, issueId);
66
+ return 0;
67
+ }
68
+
69
+ if (sub === "comment") {
70
+ const id = args.positionals[2];
71
+ const body = args.flags.message ?? args.flags.body;
72
+ if (!id || !body) {
73
+ args.io.writeLine(args.io.err, "usage: git a5c issue comment <id> -m <text>");
74
+ return 2;
75
+ }
76
+ const actor = process.env.A5C_ACTOR ?? (await gitConfigGet(args.repoRoot, "user.name")) ?? "unknown";
77
+ const commentId = args.flags.commentId ?? `c-${new UlidGenerator().generate()}`;
78
+ const time = new Date(args.nowMs()).toISOString();
79
+ const persisted = (await loadHlcState(actor)) ?? { wallMs: 0, counter: 0 };
80
+ const clock = new HlcClock(persisted);
81
+ let nonce = 0;
82
+ const ctx = { repoRoot: args.repoRoot, actor, clock, nextNonce: () => String(++nonce).padStart(4, "0") };
83
+ const res = await writeCommentCreated(ctx, { entity: { type: "issue", id }, commentId, body: String(body), time });
84
+ await saveHlcState(actor, clock.now());
85
+ if (args.flags.stageOnly || args.flags.commit) await stageFiles(args.repoRoot, [res.path]);
86
+ if (args.flags.commit) {
87
+ const msg = args.flags.message ?? `a5c: issue comment ${id}`;
88
+ await git(["-c", "user.name=a5c", "-c", "user.email=a5c@example.invalid", "commit", "-m", msg], args.repoRoot);
89
+ }
90
+ args.io.writeLine(args.io.out, commentId);
91
+ return 0;
92
+ }
93
+
94
+ if (sub === "edit-comment") {
95
+ const commentId = args.positionals[2] ?? args.flags.commentId;
96
+ const body = args.flags.message ?? args.flags.body;
97
+ const id = args.flags.id; // entity id required for now
98
+ if (!commentId || !body || !id) {
99
+ args.io.writeLine(args.io.err, "usage: git a5c issue edit-comment <commentId> --id <issueId> -m <text>");
100
+ return 2;
101
+ }
102
+ const actor = process.env.A5C_ACTOR ?? (await gitConfigGet(args.repoRoot, "user.name")) ?? "unknown";
103
+ const time = new Date(args.nowMs()).toISOString();
104
+ const persisted = (await loadHlcState(actor)) ?? { wallMs: 0, counter: 0 };
105
+ const clock = new HlcClock(persisted);
106
+ let nonce = 0;
107
+ const ctx = { repoRoot: args.repoRoot, actor, clock, nextNonce: () => String(++nonce).padStart(4, "0") };
108
+ const res = await writeCommentEdited(ctx, { entity: { type: "issue", id }, commentId, body: String(body), time });
109
+ await saveHlcState(actor, clock.now());
110
+ if (args.flags.stageOnly || args.flags.commit) await stageFiles(args.repoRoot, [res.path]);
111
+ if (args.flags.commit) {
112
+ const msg = args.flags.message ?? `a5c: issue edit-comment ${id} ${commentId}`;
113
+ await git(["-c", "user.name=a5c", "-c", "user.email=a5c@example.invalid", "commit", "-m", msg], args.repoRoot);
114
+ }
115
+ args.io.writeLine(args.io.out, res.path);
116
+ return 0;
117
+ }
118
+
119
+ if (sub === "redact-comment") {
120
+ const commentId = args.positionals[2] ?? args.flags.commentId;
121
+ const id = args.flags.id;
122
+ if (!commentId || !id) {
123
+ args.io.writeLine(args.io.err, "usage: git a5c issue redact-comment <commentId> --id <issueId> [--reason ...]");
124
+ return 2;
125
+ }
126
+ const actor = process.env.A5C_ACTOR ?? (await gitConfigGet(args.repoRoot, "user.name")) ?? "unknown";
127
+ const time = new Date(args.nowMs()).toISOString();
128
+ const persisted = (await loadHlcState(actor)) ?? { wallMs: 0, counter: 0 };
129
+ const clock = new HlcClock(persisted);
130
+ let nonce = 0;
131
+ const ctx = { repoRoot: args.repoRoot, actor, clock, nextNonce: () => String(++nonce).padStart(4, "0") };
132
+ const res = await writeCommentRedacted(ctx, { entity: { type: "issue", id }, commentId, reason: args.flags.reason, time });
133
+ await saveHlcState(actor, clock.now());
134
+ if (args.flags.stageOnly || args.flags.commit) await stageFiles(args.repoRoot, [res.path]);
135
+ if (args.flags.commit) {
136
+ const msg = args.flags.message ?? `a5c: issue redact-comment ${id} ${commentId}`;
137
+ await git(["-c", "user.name=a5c", "-c", "user.email=a5c@example.invalid", "commit", "-m", msg], args.repoRoot);
138
+ }
139
+ args.io.writeLine(args.io.out, res.path);
140
+ return 0;
141
+ }
142
+
143
+ return;
144
+ }
145
+
146
+