total-recall-brain 3.43.3 → 3.45.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.
Files changed (252) hide show
  1. package/bin/total-recall.mjs +6 -4
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +16 -9
  4. package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
  5. package/frontend/dist/index.html +1 -1
  6. package/frontend/src/App.tsx +2 -2
  7. package/frontend/src/pages/PluginsPage.tsx +1 -1
  8. package/package.json +3 -3
  9. package/scaffold/.agent/skills/plugin/SKILL.md +7 -2
  10. package/scaffold/.agent/skills/plugin/evals/README.md +5 -0
  11. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +1 -0
  12. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +1 -0
  13. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +1 -0
  14. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +1 -0
  15. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +1 -0
  16. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +1 -0
  17. package/scaffold/.agent/skills/plugin/evals/cases/07-missing-reuse.mjs +11 -0
  18. package/scaffold/.agent/skills/plugin/evals/cases/08-unresolved-owner-command.mjs +12 -0
  19. package/scaffold/.agent/skills/plugin/evals/cases/09-missing-brick.mjs +12 -0
  20. package/scaffold/.agent/skills/plugin/prompts/01-function.md +4 -3
  21. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +1 -0
  22. package/scaffold/.agent/skills/plugin/references/surfaces.md +12 -2
  23. package/scaffold/.agent/skills/plugin/scripts/record.mjs +59 -2
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +8 -0
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +15 -14
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -3
  27. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-gh-login.mjs +102 -0
  29. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +38 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +157 -13
  31. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +161 -17
  33. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  34. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
  39. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  40. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
  49. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
  50. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  51. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  52. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +15 -14
  53. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -3
  54. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  55. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +15 -14
  56. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -3
  57. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  58. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +15 -14
  59. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -3
  60. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  61. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
  63. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +17 -16
  64. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +5 -4
  65. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
  87. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +15 -14
  88. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -3
  89. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  90. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +59 -10
  91. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  92. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  93. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +160 -29
  94. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +15 -14
  95. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +4 -4
  96. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +231 -16
  97. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +19 -4
  98. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +104 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  100. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +101 -16
  101. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +54 -21
  103. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +40 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +68 -13
  105. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  106. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +64 -15
  107. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  109. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +249 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +6 -12
  112. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +16 -8
  115. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +26 -2
  116. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +6 -1
  117. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +6 -2
  118. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  119. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +28 -12
  121. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +28 -6
  122. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  129. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  130. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  131. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +110 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  133. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +597 -70
  134. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  135. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
  136. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
  137. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
  138. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +15 -14
  139. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -3
  140. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  141. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  142. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
  143. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +15 -14
  144. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -3
  145. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  146. package/scaffold/.agent/skills/total-recall/plugins/ssss/assets/scaffold.json +5 -5
  147. package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +6 -6
  148. package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -2
  149. package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +1 -1
  150. package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
  151. package/src/cli/agent.mjs +1 -1
  152. package/src/cli/command.mjs +41 -2
  153. package/src/cli/connect.mjs +2 -29
  154. package/src/cli/context.mjs +1 -0
  155. package/src/cli/dream.mjs +93 -69
  156. package/src/cli/harness.mjs +4 -4
  157. package/src/cli/help.mjs +1 -1
  158. package/src/cli/ingest-okf.mjs +4 -4
  159. package/src/cli/init.mjs +0 -25
  160. package/src/cli/lessons.mjs +51 -0
  161. package/src/cli/memory-plugin.mjs +1 -1
  162. package/src/cli/plugin/copies.mjs +64 -14
  163. package/src/cli/plugin/create.mjs +6 -7
  164. package/src/cli/plugin/index.mjs +11 -7
  165. package/src/cli/plugin/install.mjs +10 -15
  166. package/src/cli/plugin/remove.mjs +1 -1
  167. package/src/cli/plugin/search.mjs +2 -2
  168. package/src/cli/preflight.mjs +19 -6
  169. package/src/cli/proposals.mjs +1 -1
  170. package/src/cli/recall.mjs +66 -23
  171. package/src/cli/release.mjs +4 -1
  172. package/src/cli/remember.mjs +3 -14
  173. package/src/cli/server.mjs +18 -0
  174. package/src/cli/startup.mjs +1 -0
  175. package/src/cli/task.mjs +9 -2
  176. package/src/cli/update.mjs +10 -3
  177. package/src/core/access-ledger.mjs +78 -0
  178. package/src/core/agent-manager.mjs +2 -2
  179. package/src/core/automatic-brain-updates.mjs +11 -6
  180. package/src/core/brain-locate.mjs +15 -0
  181. package/src/core/brain-updates.mjs +42 -12
  182. package/src/core/claude-hooks.mjs +3 -1
  183. package/src/core/cleanup-task.mjs +15 -0
  184. package/src/core/cli-commands.mjs +2 -0
  185. package/src/core/cli-maintenance.mjs +4 -1
  186. package/src/core/cli-semantic-index.mjs +23 -3
  187. package/src/core/context-compactor.mjs +7 -2
  188. package/src/core/context-compiler.mjs +26 -1
  189. package/src/core/context-policy.mjs +7 -3
  190. package/src/core/daemon-control.mjs +22 -0
  191. package/src/core/daemon-loop.mjs +39 -62
  192. package/src/core/default-plugins.mjs +1 -1
  193. package/src/core/dream.mjs +33 -105
  194. package/src/core/ensure-dependencies.mjs +148 -0
  195. package/src/core/fast-recall.mjs +2 -2
  196. package/src/core/field-evaluation.mjs +23 -0
  197. package/src/core/gate-mode.mjs +1 -1
  198. package/src/core/global-brain-updates.mjs +1 -1
  199. package/src/core/harness-hooks.mjs +14 -2
  200. package/src/core/lesson-hooks.mjs +74 -0
  201. package/src/core/lesson-memory.mjs +24 -0
  202. package/src/core/lesson-sessions.mjs +52 -0
  203. package/src/core/lessons.mjs +96 -0
  204. package/src/core/mesh-brain-updates.mjs +2 -1
  205. package/src/core/meta-harness.mjs +2 -2
  206. package/src/core/optimizer.mjs +66 -165
  207. package/src/core/plugin-contracts/card.mjs +3 -0
  208. package/src/core/plugin-loader.mjs +12 -3
  209. package/src/core/plugin-runtime.mjs +40 -1
  210. package/src/core/plugin-store.mjs +35 -23
  211. package/src/core/post-mortem.mjs +52 -36
  212. package/src/core/preflight-gate.mjs +23 -2
  213. package/src/core/proposal-applier.mjs +31 -2
  214. package/src/core/repo-sync.mjs +4 -6
  215. package/src/core/required-plugins.mjs +26 -4
  216. package/src/core/rule-length.mjs +23 -0
  217. package/src/core/runtime.mjs +1 -1
  218. package/src/core/scheduler.mjs +35 -43
  219. package/src/core/schema.mjs +1 -2
  220. package/src/core/search.mjs +4 -3
  221. package/src/core/server-restart.mjs +128 -7
  222. package/src/core/server-service.mjs +148 -0
  223. package/src/core/session-watcher.mjs +19 -1
  224. package/src/core/skill-gap.mjs +16 -0
  225. package/src/core/ssss-kernel-bridge.mjs +33 -6
  226. package/src/core/startup-health.mjs +6 -1
  227. package/src/core/surface.mjs +68 -57
  228. package/src/core/task-context.mjs +9 -8
  229. package/src/core/task-executors.mjs +54 -106
  230. package/src/core/validated-write.mjs +5 -0
  231. package/src/core/vault-files.mjs +8 -2
  232. package/src/core/vault.mjs +1 -1
  233. package/src/core/vector-field.mjs +134 -192
  234. package/src/server/index.mjs +3 -1
  235. package/src/server/invoke-core-child.mjs +24 -0
  236. package/src/server/invoke-core-operations.mjs +102 -0
  237. package/src/server/route-manifest.json +0 -56
  238. package/src/server/routes/brains.mjs +0 -18
  239. package/src/server/routes/context.mjs +0 -41
  240. package/src/server/routes/invoke.mjs +26 -1
  241. package/src/server/routes/ssss.mjs +0 -169
  242. package/src/server/routes/system.mjs +0 -15
  243. package/src/server/routes/tasks.mjs +0 -26
  244. package/src/server/routes/vault.mjs +2 -23
  245. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  246. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  247. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  248. package/src/core/append-log.mjs +0 -131
  249. package/src/core/plugin-tasks.mjs +0 -173
  250. package/templates/onboarding-interview.md +0 -82
  251. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
  252. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
@@ -1,24 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import { execFileSync } from 'node:child_process';
3
- import { realpathSync } from 'node:fs';
2
+ import { execFileSync, spawnSync } from 'node:child_process';
3
+ import { readdirSync, realpathSync } from 'node:fs';
4
4
  import path from 'node:path';
5
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
6
  import { parseArgs } from 'node:util';
6
7
 
7
8
  const GIT_TIMEOUT_MS = 15000;
8
9
  const GIT_MAX_BUFFER = 64 * 1024 * 1024;
9
- const VERBS = ['audit', 'diff', 'identity', 'worktree-list', 'worktree-create', 'worktree-remove', 'help'];
10
+ const VERBS = ['audit', 'diff', 'identity', 'worktree-list', 'worktree-create', 'worktree-remove'];
10
11
 
11
12
  /**
12
13
  * Run git without throwing. Never trims stdout (porcelain rows begin with
13
14
  * significant spaces) and never forwards stderr to the caller's terminal.
14
15
  * @returns {{ ok: boolean, code: number|null, stdout: string, stderr: string, missing: boolean }}
15
16
  */
16
- export function git(args, cwd = process.cwd()) {
17
+ export function git(args, cwd = process.cwd(), input) {
17
18
  try {
18
19
  const stdout = execFileSync('git', args, {
19
20
  cwd,
20
21
  encoding: 'utf8',
21
- stdio: ['ignore', 'pipe', 'pipe'],
22
+ ...(input === undefined ? { stdio: ['ignore', 'pipe', 'pipe'] } : { input, stdio: ['pipe', 'pipe', 'pipe'] }),
22
23
  timeout: GIT_TIMEOUT_MS,
23
24
  maxBuffer: GIT_MAX_BUFFER,
24
25
  env: { ...process.env, LC_ALL: 'C', GIT_OPTIONAL_LOCKS: '0', GIT_TERMINAL_PROMPT: '0' },
@@ -210,14 +211,20 @@ function printIdentity(r) {
210
211
  console.log('');
211
212
  }
212
213
 
213
- function usage() {
214
- return `Usage: total-recall git-sentinel [operation] [--json]
215
- audit | identity
216
- diff [--patch] [--cached] [--base <ref>]
217
- worktree list
218
- worktree create <path> [--base <ref>]
219
- worktree remove <path> [--force]
220
- worktree-list | worktree-create | worktree-remove (separate operation effects)`;
214
+ function usage(operations = {}) {
215
+ const features = Object.entries(operations).sort(([a], [b]) => a.localeCompare(b))
216
+ .map(([name, op]) => ` ${(op.usage || name).split('\n')[0].padEnd(46)} ${op.summary}`).join('\n');
217
+ return `Usage: total-recall git <command> [arguments] [--json]
218
+
219
+ Every git command works here: anything that is not listed below runs git itself, with your
220
+ arguments, git's hooks, output and exit status. Example: total-recall git pull --rebase.
221
+ Git's own help for a command: total-recall git help <command>.
222
+
223
+ Plugin operations:
224
+ audit | identity Repository state; commit identity and its history
225
+ diff --json [--patch] [--cached] [--base <ref>] The card's diff (plain diff is git's)
226
+ worktree-list | worktree-create <path> [--base <ref>] | worktree-remove <path> [--force]
227
+ ${features ? features + '\n' : ''} --help This help; <operation> --help for one operation`;
221
228
  }
222
229
 
223
230
  function gitResult(args, cwd) {
@@ -303,36 +310,141 @@ function printAudit(r) {
303
310
  console.log(` Head Commit: ${fmt(r.lastCommit, unk)}\n`);
304
311
  }
305
312
 
313
+ /**
314
+ * Feature operations live in src/ops/<name>.mjs, one file per feature. Each module may export:
315
+ * - `operations`: { <name>: { summary, usage, run(args, ctx) } }; `run` returns an exit status.
316
+ * - `guards`: [{ name, before(args, ctx) }] run before git's own commands, in file order. `before`
317
+ * returns nothing to allow, `{ args }` to rewrite the arguments, or `{ refuse: <message> }`.
318
+ * An optional `after(args, ctx, status)` runs after git finishes; its failure is reported, never fatal.
319
+ * `ctx` is { cwd, env, json, git(args) -> spawnSync result with captured output }.
320
+ * Help is built from this registry, so every operation is in `--help`.
321
+ */
322
+ const OPS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), 'src', 'ops');
323
+ let GIT_COMMANDS;
324
+ function isGitCommand(name) {
325
+ if (!GIT_COMMANDS) GIT_COMMANDS = new Set(String(spawnSync('git', ['--list-cmds=main,others,alias,nohelpers'], { encoding: 'utf8' }).stdout || '').split('\n').filter(Boolean));
326
+ return GIT_COMMANDS.has(name);
327
+ }
328
+
329
+ export async function loadRegistry(dir = OPS_DIR) {
330
+ const operations = {}, guards = [];
331
+ let files = [];
332
+ try { files = readdirSync(dir).filter(f => f.endsWith('.mjs') && !f.includes('.spec.') && !f.includes('.test.')).sort(); } catch { /* no feature modules */ }
333
+ for (const file of files) {
334
+ // One module that fails to load disables only its own operations; git and every other operation keep working.
335
+ let mod;
336
+ try { mod = await import(pathToFileURL(path.join(dir, file)).href); }
337
+ catch (error) {
338
+ const name = file.replace(/\.mjs$/, '');
339
+ if (!operations[name]) operations[name] = { summary: `unavailable: ${String(error.message).split('\n')[0]}`, usage: name, file, broken: true,
340
+ run: () => { console.error(`${name} is unavailable: ${error.message.split('\n')[0]}`); return 1; } };
341
+ continue;
342
+ }
343
+ for (const [name, op] of Object.entries(mod.operations || {})) {
344
+ if (operations[name]) throw new Error(`Operation "${name}" is defined twice (${file})`);
345
+ operations[name] = { ...op, file };
346
+ }
347
+ for (const guard of mod.guards || []) guards.push({ ...guard, file });
348
+ }
349
+ return { operations, guards };
350
+ }
351
+
352
+ function context(cwd, json) {
353
+ return {
354
+ cwd, env: process.env, json,
355
+ git: (args, options = {}) => spawnSync('git', args, { cwd, env: process.env, encoding: 'utf8', maxBuffer: GIT_MAX_BUFFER, ...options }),
356
+ };
357
+ }
358
+
359
+ /**
360
+ * Plugin operations: audit, identity, help, the checked commit, the hyphenated worktree-* operations
361
+ * and `diff` with --json or --base (the card's diff). Everything else is git's own CLI.
362
+ * `commit` stays a plugin operation only in its checked form (-m/--message, -a/--all, paths).
363
+ * Any other git commit flag (--amend, --fixup, -S and the rest) goes to git's own commit.
364
+ */
365
+ export function isPassthrough(args, featureOps = []) {
366
+ const [first] = args;
367
+ if (first === undefined) return false;
368
+ if (featureOps.includes(first)) return false;
369
+ if (first.startsWith('-')) return !['--json', '--help', '-h'].includes(first);
370
+ if (['audit', 'identity', 'worktree-list', 'worktree-create', 'worktree-remove'].includes(first)) return false;
371
+ if (first === 'diff') return !args.includes('--json') && !args.some(a => a === '--base' || a.startsWith('--base='));
372
+ return true;
373
+ }
374
+
306
375
  export async function run(argv = []) {
307
- // argv: ["node", "total-recall", "git-sentinel", "<subcommand>", ...flags]
376
+ // argv: ["node", "total-recall", "git", "<subcommand>", ...flags]
308
377
  // Never process.exit(): a host running this in-process would be stopped too.
309
378
  const clean = Array.isArray(argv) ? argv.slice(3) : [];
379
+ const registry = await loadRegistry();
380
+ const cwd0 = process.cwd();
381
+ const featureOp = registry.operations[clean[0]];
382
+ const wantsOpHelp = clean.includes('--help') || clean.includes('-h');
383
+ // Guards see every command, plugin operations included (a plugin `commit` is still a commit).
384
+ // Each guard decides from args[0] whether the command concerns it.
385
+ const runGuards = async (args, phase, status) => {
386
+ for (const guard of registry.guards) {
387
+ if (phase === 'before') {
388
+ const verdict = await guard.before?.(args, context(cwd0, false));
389
+ if (verdict?.refuse) return { refuse: `${args[0]} refused by ${guard.name}: ${verdict.refuse}` };
390
+ if (verdict?.args) args = verdict.args;
391
+ } else if (guard.after) {
392
+ try { await guard.after(args, context(cwd0, false), status); }
393
+ catch (error) { console.error(`${guard.name} after ${args[0]}: ${error.message}`); }
394
+ }
395
+ }
396
+ return { args };
397
+ };
398
+ // `-h` on a plugin operation that is also a git command (commit, bundle) is git's own usage;
399
+ // `--help` shows the plugin operation's help.
400
+ const gitUsage = featureOp && clean.includes('-h') && !clean.includes('--help') && isGitCommand(clean[0]);
401
+ if (featureOp && !wantsOpHelp || gitUsage) {
402
+ if (gitUsage) {
403
+ const ran = spawnSync('git', clean, { cwd: cwd0, stdio: 'inherit', env: process.env });
404
+ process.exitCode = ran.status ?? 1;
405
+ return;
406
+ }
407
+ const checked = await runGuards(clean, 'before');
408
+ if (checked.refuse) { console.error(checked.refuse); process.exitCode = 1; return; }
409
+ const status = await featureOp.run(checked.args.slice(1).filter(a => a !== '--json'), context(cwd0, clean.includes('--json')));
410
+ process.exitCode = typeof status === 'number' ? status : 0;
411
+ await runGuards(checked.args, 'after', process.exitCode);
412
+ return;
413
+ }
414
+ if (featureOp) { console.log(`total-recall git ${clean[0]} — ${featureOp.summary}\n${featureOp.usage || ''}`.trimEnd()); return; }
415
+ // The whole git CLI: every command that is not a plugin operation runs git itself, with the
416
+ // caller's arguments unchanged (unless a guard rewrites them), git's own hooks, output and exit status.
417
+ if (isPassthrough(clean, Object.keys(registry.operations))) {
418
+ const checked = await runGuards(clean, 'before');
419
+ if (checked.refuse) { console.error(`git ${checked.refuse}`); process.exitCode = 1; return; }
420
+ const ran = spawnSync('git', checked.args, { cwd: cwd0, stdio: 'inherit', env: process.env });
421
+ if (ran.error) console.error(`git could not start: ${ran.error.message}`);
422
+ process.exitCode = ran.status ?? 1;
423
+ await runGuards(checked.args, 'after', process.exitCode);
424
+ return;
425
+ }
310
426
  const isJson = clean.includes('--json');
311
427
  const wantsHelp = clean.includes('--help') || clean.includes('-h');
312
428
  let [action = 'audit', ...args] = clean.filter(arg => arg !== '--json');
313
- if (action === 'worktree') {
314
- const [verb = 'list', ...rest] = args;
315
- action = `worktree-${verb}`;
316
- args = rest;
317
- }
318
429
  const fail = (message, code = 1, extra = {}) => {
319
430
  process.exitCode = code;
320
431
  if (isJson) console.log(JSON.stringify({ ok: false, error: message, ...extra }, null, 2));
321
432
  else console.error(message);
322
433
  };
323
434
 
324
- if (wantsHelp || action === 'help') {
325
- console.log(usage());
435
+ if (wantsHelp) {
436
+ console.log(usage(registry.operations));
326
437
  return;
327
438
  }
328
439
  if (!VERBS.includes(action)) {
329
- fail(`Unknown git-sentinel command "${action}". ${usage()}`, 2);
440
+ fail(`Unknown git plugin command "${action}". ${usage()}`, 2);
330
441
  return;
331
442
  }
332
443
 
333
444
  const options = action === 'diff' ? { patch: { type: 'boolean' }, cached: { type: 'boolean' }, base: { type: 'string' } }
334
445
  : action === 'worktree-create' ? { base: { type: 'string' } }
335
- : action === 'worktree-remove' ? { force: { type: 'boolean' } } : {};
446
+ : action === 'worktree-remove' ? { force: { type: 'boolean' } }
447
+ : {};
336
448
  const targetRequired = action === 'worktree-create' || action === 'worktree-remove';
337
449
  let parsed;
338
450
  try {
@@ -1,18 +1,18 @@
1
1
  {
2
- "name": "tr-plugin-git-sentinel",
3
- "version": "1.1.4",
2
+ "name": "tr-plugin-git",
3
+ "version": "1.1.5",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
- "name": "tr-plugin-git-sentinel",
9
- "version": "1.1.4",
8
+ "name": "tr-plugin-git",
9
+ "version": "1.1.5",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@gregiteen/ssss-cli": "0.14.4"
12
+ "@gregiteen/ssss-cli": "0.14.5"
13
13
  },
14
14
  "devDependencies": {
15
- "total-recall-brain": "3.42.1",
15
+ "total-recall-brain": "3.44.0",
16
16
  "vitest": "^4.1.10"
17
17
  }
18
18
  },
@@ -716,9 +716,9 @@
716
716
  "license": "MIT"
717
717
  },
718
718
  "node_modules/@gregiteen/ssss-cli": {
719
- "version": "0.14.4",
720
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.4.tgz",
721
- "integrity": "sha512-R6JIgPRvS9xXp0xb6BYO3rauv+72r35GIiDrJcvbjPdTSHFZXr8/jRxFOgoF8yTed/5xyGFF3ey3DI9bOjmHVA==",
719
+ "version": "0.14.5",
720
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.5.tgz",
721
+ "integrity": "sha512-qVEYOocnwn97uJ0eDzmuwN06CYsYWr2t3+iKylpisBHX4wlLWk7P8EmFFdBlx7NyKVIZDDeYT164Gd7/T8UGgw==",
722
722
  "license": "ISC",
723
723
  "bin": {
724
724
  "ssss": "scripts/ssss.mjs"
@@ -1888,9 +1888,9 @@
1888
1888
  },
1889
1889
  "node_modules/@ssss/cli": {
1890
1890
  "name": "@gregiteen/ssss-cli",
1891
- "version": "0.14.4",
1892
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.4.tgz",
1893
- "integrity": "sha512-R6JIgPRvS9xXp0xb6BYO3rauv+72r35GIiDrJcvbjPdTSHFZXr8/jRxFOgoF8yTed/5xyGFF3ey3DI9bOjmHVA==",
1891
+ "version": "0.14.5",
1892
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.5.tgz",
1893
+ "integrity": "sha512-qVEYOocnwn97uJ0eDzmuwN06CYsYWr2t3+iKylpisBHX4wlLWk7P8EmFFdBlx7NyKVIZDDeYT164Gd7/T8UGgw==",
1894
1894
  "dev": true,
1895
1895
  "license": "ISC",
1896
1896
  "bin": {
@@ -6533,9 +6533,9 @@
6533
6533
  }
6534
6534
  },
6535
6535
  "node_modules/total-recall-brain": {
6536
- "version": "3.42.1",
6537
- "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.42.1.tgz",
6538
- "integrity": "sha512-woH7mdkGwPFYlKPZurhE8L3BPe+cSeIruNGaPOjGZJm/O8C92Bbr1gsdOY0tTLg5QHlgPHcXG1GMtsm9lPDz8w==",
6536
+ "version": "3.44.0",
6537
+ "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.44.0.tgz",
6538
+ "integrity": "sha512-VNPaIO9zsSwoxAsJ5ByIQBP9zPpLq4RiQO7Vrex4rBOHFQJT50P7utcZiLMSZTyZJU4C/m5uB8qTCSBMp6+l6Q==",
6539
6539
  "dev": true,
6540
6540
  "license": "MIT",
6541
6541
  "workspaces": [
@@ -6543,7 +6543,7 @@
6543
6543
  ],
6544
6544
  "dependencies": {
6545
6545
  "@simplewebauthn/server": "^13.3.2",
6546
- "@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.4",
6546
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.5",
6547
6547
  "bcrypt": "^6.0.0",
6548
6548
  "better-sqlite3": "^12.11.1",
6549
6549
  "cookie-parser": "^1.4.7",
@@ -6566,6 +6566,7 @@
6566
6566
  "antigravity": "bin/antigravity.mjs",
6567
6567
  "total-recall": "bin/total-recall.mjs",
6568
6568
  "total-recall-memory": "bin/total-recall-memory.mjs",
6569
+ "total-recall-server-release": "bin/total-recall-server-release.mjs",
6569
6570
  "vibebrain": "bin/total-recall.mjs"
6570
6571
  },
6571
6572
  "optionalDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "tr-plugin-git-sentinel",
3
- "version": "1.1.4",
2
+ "name": "tr-plugin-git",
3
+ "version": "1.1.5",
4
4
  "type": "module",
5
5
  "description": "Reports the state of the current git repository: branch, dirty files, unpushed commits and stashes.",
6
6
  "scripts": {
@@ -11,6 +11,7 @@
11
11
  "files": [
12
12
  "cli.mjs",
13
13
  "plugin.json",
14
+ "src/",
14
15
  "README.md",
15
16
  "LICENSE",
16
17
  "ui/"
@@ -19,9 +20,9 @@
19
20
  "access": "public"
20
21
  },
21
22
  "dependencies": {
22
- "@gregiteen/ssss-cli": "0.14.4"
23
+ "@gregiteen/ssss-cli": "0.14.5"
23
24
  },
24
25
  "overrides": {
25
- "@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.4"
26
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.5"
26
27
  }
27
28
  }
@@ -0,0 +1,161 @@
1
+ {
2
+ "$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
3
+ "id": "git",
4
+ "name": "Git",
5
+ "version": "2.0.1",
6
+ "description": "The whole git CLI through Total Recall: sign-in from the auth store, guarded commits and pushes, undo, fleet state, and the intent behind every change.",
7
+ "author": "Total Recall",
8
+ "license": "MIT",
9
+ "use_cases": [
10
+ "software-development"
11
+ ],
12
+ "gate": {
13
+ "tasks": ["Run any git command", "Commit or push with identity, secret and rule checks", "Sign git in through the auth store", "Undo a destructive git command", "Check repository state across the mesh", "Keep the git CLI current on every node"],
14
+ "owned_commands": [
15
+ { "pattern": "(^|[;&|(]\\s*)git(\\s+-C\\s+\\S+)?\\s+[a-z]", "use": "total-recall git <same git command and arguments>" }
16
+ ]
17
+ },
18
+ "tasks": [
19
+ { "intent": "Check and update the git CLI", "schedule": "17 6 * * *", "command": "cli update --scheduled", "placement": "per-node" }
20
+ ],
21
+ "cli": {
22
+ "answers_help": true,
23
+ "command": "git",
24
+ "handler": "./cli.mjs",
25
+ "subcommands": [
26
+ {
27
+ "name": "audit",
28
+ "description": "Summarise branch, worktree, upstream and stash state (--json for machine output)",
29
+ "effect": "read"
30
+ },
31
+ {
32
+ "name": "diff",
33
+ "description": "Read a stat or patch for unstaged, staged or base-relative changes",
34
+ "effect": "read",
35
+ "args": [
36
+ { "name": "patch", "flag": "--patch", "type": "boolean", "label": "Show patch" },
37
+ { "name": "cached", "flag": "--cached", "type": "boolean", "label": "Use staged changes" },
38
+ { "name": "base", "flag": "--base", "type": "string", "label": "Base ref" }
39
+ ]
40
+ },
41
+ { "name": "commit", "description": "Check identity, conflicts, HEAD, secrets and repository rules, then run git's own commit with your arguments", "effect": "write", "timeout_ms": 600000, "args": [
42
+ { "name": "args", "type": "string", "label": "git commit arguments", "positional": true }
43
+ ] },
44
+ { "name": "push", "description": "Check outgoing commits for secrets and rules, turn --force into --force-with-lease, then run git's own push", "effect": "write", "timeout_ms": 600000, "args": [
45
+ { "name": "args", "type": "string", "label": "git push arguments", "positional": true }
46
+ ] },
47
+ { "name": "credential-helper", "description": "Git credential helper: give git the mapped auth-store credential (get); store and erase do nothing", "effect": "read" },
48
+ { "name": "auth", "description": "Sign-in: status (live check, no secret shown), map <host> <KEY>, setup (helper entries for this machine)", "effect": "write", "args": [
49
+ { "name": "action", "type": "string", "label": "status | map | setup", "positional": true }
50
+ ] },
51
+ { "name": "cli", "description": "Git CLI metadata (version, path, install source, upstream, release notes); --all-nodes for the mesh; cli update updates git through its installer", "effect": "write", "timeout_ms": 600000 },
52
+ { "name": "undo", "description": "Restore refs, index and files from the snapshot taken before a destructive git command; --list shows snapshots", "effect": "destructive", "args": [
53
+ { "name": "event", "type": "string", "label": "Snapshot", "positional": true },
54
+ { "name": "list", "flag": "--list", "type": "boolean", "label": "List snapshots" }
55
+ ] },
56
+ { "name": "fleet", "description": "Dirty, unpushed, behind, diverged, stale stashes and abandoned worktrees across registered repositories; --all-nodes for the mesh", "effect": "read", "timeout_ms": 600000 },
57
+ { "name": "where", "description": "Machines and checkouts that contain a commit", "effect": "read", "args": [
58
+ { "name": "commit", "type": "string", "label": "Commit", "required": true, "positional": true }
59
+ ] },
60
+ { "name": "why", "description": "Blame one line plus the session, tracker item, decision and rules recorded when it was committed", "effect": "read", "args": [
61
+ { "name": "location", "type": "string", "label": "file:line", "required": true, "positional": true }
62
+ ] },
63
+ { "name": "group", "description": "Group a dirty tree into commits by the agent session that wrote each change; --apply commits the groups", "effect": "write" },
64
+ { "name": "message", "description": "Draft a commit message from the staged diff, the active tracker and its action log", "effect": "read" },
65
+ { "name": "bisect-agent", "description": "Find the first bad commit with the repository's quality gate as the test", "effect": "write", "timeout_ms": 600000, "args": [
66
+ { "name": "good", "type": "string", "label": "Good commit", "required": true, "positional": true },
67
+ { "name": "bad", "type": "string", "label": "Bad commit", "required": true, "positional": true }
68
+ ] },
69
+ { "name": "resolve", "description": "Propose a resolution for each conflicted file from both sides' recorded intent; --apply writes it", "effect": "write" },
70
+ { "name": "handoff", "description": "Pack a branch, its tracker and related memory into one SSSS bundle for another agent or machine", "effect": "write", "args": [
71
+ { "name": "branch", "type": "string", "label": "Branch", "required": true, "positional": true },
72
+ { "name": "out", "flag": "--out", "type": "string", "label": "Output file" }
73
+ ] },
74
+ { "name": "worktree-list", "description": "Read registered worktrees for the current repository", "effect": "read" },
75
+ { "name": "worktree-create", "description": "Create a detached worktree at a specified path and base ref", "effect": "write", "args": [
76
+ { "name": "path", "type": "string", "label": "Worktree path", "required": true, "positional": true },
77
+ { "name": "base", "flag": "--base", "type": "string", "label": "Base ref" }
78
+ ] },
79
+ { "name": "worktree-remove", "description": "Remove an owned linked worktree, with explicit force for dirty content", "effect": "destructive", "args": [
80
+ { "name": "path", "type": "string", "label": "Worktree path", "required": true, "positional": true },
81
+ { "name": "force", "flag": "--force", "type": "boolean", "label": "Allow removal of dirty content" }
82
+ ] },
83
+ { "name": "worktree", "description": "Compatibility form: worktree list, create or remove", "effect": "destructive" },
84
+ {
85
+ "name": "identity",
86
+ "description": "Show who commits here and which identities the history publishes; exits 1 on a missing or machine-derived identity (--json for machine output)",
87
+ "effect": "read"
88
+ }
89
+ ]
90
+ },
91
+ "card_face": {
92
+ "skin": "copper-obsidian",
93
+ "theme": "copper",
94
+ "headline": "Worktree Hygiene & Repository Watch",
95
+ "primary_color": "#b45309",
96
+ "secondary_color": "#0c0a09",
97
+ "accent_color": "#f59e0b",
98
+ "touch_layout": "comfortable",
99
+ "interactive": true,
100
+ "quick_actions": [
101
+ {
102
+ "subcommand": "audit",
103
+ "label": "AUDIT"
104
+ },
105
+ {
106
+ "subcommand": "diff",
107
+ "label": "DIFFSTAT"
108
+ }
109
+ ]
110
+ },
111
+ "ui": {
112
+ "design_tokens": "ui/DESIGN.md",
113
+ "elements": [
114
+ {
115
+ "id": "card",
116
+ "kind": "card",
117
+ "tag": "tr-git-card",
118
+ "module": "ui/card.js",
119
+ "description": "Git Hygiene Repository Hygiene Card Face"
120
+ }
121
+ ]
122
+ },
123
+ "auth": {
124
+ "plugin": "auth",
125
+ "methods": [
126
+ "passkey",
127
+ "google-oauth2"
128
+ ],
129
+ "session_verification": true
130
+ },
131
+ "deploy": {
132
+ "targets": [
133
+ "ssss-app",
134
+ "nextjs",
135
+ "react",
136
+ "flask"
137
+ ],
138
+ "required_ssss_version": ">=0.9.3",
139
+ "access_grants": [
140
+ "auth:session:verify",
141
+ "ssss:vault:read",
142
+ "ssss:vault:write",
143
+ "ssss:events:append"
144
+ ]
145
+ },
146
+ "branding": {
147
+ "name": "Git Hygiene",
148
+ "color": "#b45309",
149
+ "accent": "#f59e0b",
150
+ "badge": "Repository Hygiene",
151
+ "font": "space-grotesk"
152
+ },
153
+ "status": {
154
+ "command": "audit",
155
+ "refresh_seconds": 60
156
+ },
157
+ "distribution": {
158
+ "tier": "official",
159
+ "license": "open"
160
+ }
161
+ }
@@ -0,0 +1,94 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { createRequire } from 'node:module';
6
+ import { pathToFileURL } from 'node:url';
7
+
8
+ const require = createRequire(import.meta.url);
9
+ export function git(ctx, args, options) {
10
+ const result = ctx.git(args, options);
11
+ if ((result.status ?? result.code) !== 0 || result.error) throw new Error(`Git ${args[0]} failed`);
12
+ return String(result.stdout ?? '');
13
+ }
14
+ export function tr(ctx, args, options = {}) {
15
+ return spawnSync(ctx.env.GIT_PLUGIN_TR_BIN || 'total-recall', args, {
16
+ cwd: ctx.cwd, env: ctx.env, encoding: 'utf8', shell: false,
17
+ timeout: 30000, maxBuffer: 16 * 1024 * 1024, ...options,
18
+ });
19
+ }
20
+ export function trJSON(ctx, args) {
21
+ const result = tr(ctx, args);
22
+ if (result.error || result.status !== 0) throw new Error(`Total Recall ${args[0]} failed`);
23
+ try { return JSON.parse(result.stdout); } catch { throw new Error(`Total Recall ${args[0]} returned invalid JSON`); }
24
+ }
25
+ export function repository(ctx) {
26
+ return git(ctx, ['rev-parse', '--path-format=absolute', '--git-common-dir']).trim();
27
+ }
28
+ export async function ledger(ctx, kind = 'commits', required = false) {
29
+ const dir = ctx.env.TR_PLUGIN_STATE_DIR;
30
+ if (!dir) {
31
+ if (required) throw new Error('TR_PLUGIN_STATE_DIR must be supplied by the Total Recall host');
32
+ return null;
33
+ }
34
+ if (!path.isAbsolute(dir)) throw new Error('TR_PLUGIN_STATE_DIR must be absolute');
35
+ let modulePath;
36
+ const root = ctx.env.TR_PACKAGE_ROOT;
37
+ if (root) {
38
+ if (!path.isAbsolute(root) || JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).name !== 'total-recall-brain') throw new Error('Invalid Total Recall state host');
39
+ modulePath = path.join(root, 'src/core/plugin-state-store.mjs');
40
+ } else modulePath = require.resolve('total-recall-brain/src/core/plugin-state-store.mjs');
41
+ const { PluginStateStore } = await import(pathToFileURL(modulePath).href);
42
+ const key = crypto.createHash('sha256').update(repository(ctx)).digest('hex');
43
+ return new PluginStateStore(path.join(dir, `${kind}-${key}.jsonl`), 'git');
44
+ }
45
+ export function session(env) { return env.CLAUDE_SESSION_ID || env.CODEX_SESSION_ID || env.TR_SESSION_ID || null; }
46
+ export function trailers(message) {
47
+ // Trailers must be in the final paragraph. Body prose is not attribution.
48
+ const block = message.trim().split(/\n\s*\n/).at(-1) || '';
49
+ const result = {};
50
+ let key;
51
+ for (const line of block.split('\n')) {
52
+ const match = /^([\w-]+):\s*(.*)$/.exec(line);
53
+ if (match) { key = match[1].toLowerCase(); (result[key] ||= []).push(match[2]); }
54
+ else if (/^\s+/.test(line) && key) result[key][result[key].length - 1] += ` ${line.trim()}`;
55
+ else return {};
56
+ }
57
+ return result;
58
+ }
59
+ export function intent(message) {
60
+ const rows = trailers(message);
61
+ return {
62
+ session: rows['agent-session']?.[0] || rows['session-id']?.[0] || rows.session?.[0] || null,
63
+ trackerItems: [...new Set(message.match(/\b(?:[A-Z][A-Z0-9_]*-[A-Z]*\d[\w.-]*|P\d+\.\d+|R\d+)\b/g) || [])],
64
+ decisions: [...(rows.decision || []), ...message.split('\n').filter(row => /^Decision\s*:/i.test(row)).map(row => row.replace(/^Decision\s*:\s*/i, ''))].filter((row, i, all) => all.indexOf(row) === i),
65
+ rules: rows.rules || rows.rule || [], trailers: rows,
66
+ };
67
+ }
68
+ export async function commitContext(ctx, sha) {
69
+ const message = git(ctx, ['show', '-s', '--format=%B', sha]);
70
+ const store = await ledger(ctx);
71
+ const record = store?.read([]).findLast(row => row.type === 'git.commit_context' && row.sha === sha) || null;
72
+ return { sha, message: message.trimEnd(), recorded: record, trailers: trailers(message), ...intent(message),
73
+ ...(record ? { session: record.session, trackerItems: record.trackerItems, decisions: record.decisions, rules: record.rules } : {}) };
74
+ }
75
+ export function output(ctx, value, text) { console.log(ctx.json ? JSON.stringify(value, null, 2) : text); }
76
+ export function operation(summary, usage, run) {
77
+ return { summary, usage, async run(args, ctx) {
78
+ try { return await run(args, ctx); }
79
+ catch (error) { if (ctx.json) console.log(JSON.stringify({ ok: false, error: error.message })); else console.error(error.message); return 1; }
80
+ } };
81
+ }
82
+ export function activePlan(ctx) {
83
+ const root = git(ctx, ['rev-parse', '--show-toplevel']).trim();
84
+ const base = path.join(root, 'docs/projects/in-progress');
85
+ if (!fs.existsSync(base)) return { tracker: null, plan: null, items: [], actionLog: '', reason: 'No active plan' };
86
+ const dirs = fs.readdirSync(base, { withFileTypes: true }).filter(row => row.isDirectory());
87
+ const trackers = dirs.flatMap(dir => fs.readdirSync(path.join(base, dir.name)).filter(name => name.endsWith('_PROJECT_TRACKER.md')).map(name => path.join(base, dir.name, name)));
88
+ if (trackers.length !== 1) throw new Error('Expected one active project tracker');
89
+ const tracker = trackers[0];
90
+ const plan = tracker.replace('_PROJECT_TRACKER.md', '_DEVELOPMENT_PLAN.md');
91
+ const body = fs.readFileSync(tracker, 'utf8');
92
+ const planBody = fs.existsSync(plan) ? fs.readFileSync(plan, 'utf8') : '';
93
+ return { tracker, plan, body, planBody, items: body.split('\n').filter(row => /^- \[[ xX]\]/.test(row)), actionLog: body.split(/^## Action Log\s*$/m)[1]?.trim() || '' };
94
+ }
@@ -0,0 +1,50 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { vi } from 'vitest';
7
+
8
+ export function fixture() {
9
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'git-phase3-spec-'));
10
+ const repo = path.join(tmp, 'repo'); fs.mkdirSync(repo);
11
+ const cwd = fs.realpathSync(repo);
12
+ const env = { ...process.env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_SYSTEM: '/dev/null', GIT_CONFIG_NOSYSTEM: '1', GIT_AUTHOR_NAME: 'Test Agent', GIT_AUTHOR_EMAIL: 'agent@example.com', GIT_COMMITTER_NAME: 'Test Agent', GIT_COMMITTER_EMAIL: 'agent@example.com', TR_PLUGIN_STATE_DIR: path.join(tmp, 'state'), TR_PACKAGE_ROOT: fileURLToPath(new URL('../../node_modules/total-recall-brain', import.meta.url)), CODEX_SESSION_ID: 'session-one' };
13
+ delete env.CLAUDE_SESSION_ID;
14
+ delete env.GIT_INDEX_FILE;
15
+ // Specs run this checkout's CLI, never whatever version happens to be installed.
16
+ const CLI = new URL('../../cli.mjs', import.meta.url).href;
17
+ const RUN = `import(${JSON.stringify(CLI)}).then(m => m.run(['node', 'total-recall', 'git', ...process.argv.slice(1)])).then(() => process.exit(process.exitCode ?? 0))`;
18
+ const git = (args, options = {}) => spawnSync(process.execPath, ['--input-type=module', '-e', RUN, ...args], { cwd, env, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, ...options });
19
+ // Fixture setup uses plain git: setup is not the code under test, so no guard or record runs for it.
20
+ const raw = (args, options = {}) => spawnSync('git', args, { cwd, env, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024, ...options });
21
+ const g = (...args) => { const r = raw(args); if (r.status !== 0) throw new Error(`Fixture git ${args[0]}: ${r.stderr}`); return r.stdout; };
22
+ g('init', '-q', '-b', 'main');
23
+ g('config', '--local', 'user.name', 'Test Agent');
24
+ g('config', '--local', 'user.email', 'agent@example.com');
25
+ g('config', '--local', 'commit.gpgsign', 'false');
26
+ const write = (name, content) => { fs.mkdirSync(path.dirname(path.join(cwd, name)), { recursive: true }); fs.writeFileSync(path.join(cwd, name), content); };
27
+ const commit = (message = 'Initial', file = 'a.txt', body = 'one\ntwo\nthree\nfour\nfive\n') => { write(file, body); g('add', '--', file); g('commit', '-qm', message); return g('rev-parse', 'HEAD').trim(); };
28
+ const responses = path.join(tmp, 'responses.json');
29
+ const calls = path.join(tmp, 'calls.jsonl');
30
+ const bin = path.join(tmp, 'total-recall-stub');
31
+ fs.writeFileSync(responses, '{}');
32
+ fs.writeFileSync(bin, `#!/usr/bin/env node
33
+ const fs=require('node:fs'),cp=require('node:child_process');
34
+ const args=process.argv.slice(2);fs.appendFileSync(${JSON.stringify(calls)},JSON.stringify(args)+'\\n');
35
+ const map=JSON.parse(fs.readFileSync(${JSON.stringify(responses)},'utf8'));
36
+ if(args[0]==='git'){const r=cp.spawnSync(process.execPath,['--input-type=module','-e',${JSON.stringify(RUN)},...args.slice(1)],{env:process.env,encoding:'utf8',input:fs.readFileSync(0)});process.stdout.write(r.stdout||'');process.stderr.write(r.stderr||'');process.exitCode=r.status??1;}
37
+ else if(args[0]==='auth'&&args[2]==='list'&&!map['auth secret']){process.stdout.write(JSON.stringify({keys:[]}));}
38
+ else if(args[0]==='auth'&&args[2]==='scan'&&!map['auth secret']){fs.readFileSync(0);process.stdout.write(JSON.stringify({ok:true,leak:false,keys:[]}));}
39
+ else if(args[0]==='code-quality'&&map.gateFile){const ok=fs.readFileSync(map.gateFile,'utf8').includes('good');process.stdout.write(JSON.stringify({ok}));process.exitCode=ok?0:1;}
40
+ else{const r=map[args.slice(0,2).join(' ')]||map[args[0]]||{status:0,body:{}};process.stdout.write(typeof r.body==='string'?r.body:JSON.stringify(r.body));process.exitCode=r.status??0;}
41
+ `, { mode: 0o755 });
42
+ env.GIT_PLUGIN_TR_BIN = bin;
43
+ const set = map => fs.writeFileSync(responses, JSON.stringify(map));
44
+ const readCalls = () => fs.existsSync(calls) ? fs.readFileSync(calls, 'utf8').trim().split('\n').filter(Boolean).map(row => JSON.parse(row)) : [];
45
+ const out = vi.spyOn(console, 'log').mockImplementation(() => {});
46
+ vi.spyOn(console, 'error').mockImplementation(() => {});
47
+ const ctx = { cwd, env, json: true, git };
48
+ const result = () => JSON.parse(out.mock.calls.at(-1)[0]);
49
+ return { tmp, cwd, env, ctx, g, git, write, commit, set, readCalls, result, cleanup() { vi.restoreAllMocks(); fs.rmSync(tmp, { recursive: true, force: true }); } };
50
+ }