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.
- package/bin/total-recall.mjs +6 -4
- package/docs/ARCHITECTURE.md +10 -7
- package/docs/reference/cli-reference.md +16 -9
- package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/App.tsx +2 -2
- package/frontend/src/pages/PluginsPage.tsx +1 -1
- package/package.json +3 -3
- package/scaffold/.agent/skills/plugin/SKILL.md +7 -2
- package/scaffold/.agent/skills/plugin/evals/README.md +5 -0
- package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +1 -0
- package/scaffold/.agent/skills/plugin/evals/cases/07-missing-reuse.mjs +11 -0
- package/scaffold/.agent/skills/plugin/evals/cases/08-unresolved-owner-command.mjs +12 -0
- package/scaffold/.agent/skills/plugin/evals/cases/09-missing-brick.mjs +12 -0
- package/scaffold/.agent/skills/plugin/prompts/01-function.md +4 -3
- package/scaffold/.agent/skills/plugin/prompts/08-verify.md +1 -0
- package/scaffold/.agent/skills/plugin/references/surfaces.md +12 -2
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +59 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +8 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-gh-login.mjs +102 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +38 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +157 -13
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +161 -17
- package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
- package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
- package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +17 -16
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +5 -4
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +59 -10
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +160 -29
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +231 -16
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +19 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +104 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +101 -16
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +54 -21
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +68 -13
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +64 -15
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +249 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +6 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/pool.mjs +16 -8
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +26 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +6 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +6 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +28 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +28 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +110 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +597 -70
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +15 -14
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/ssss/assets/scaffold.json +5 -5
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
- package/src/cli/agent.mjs +1 -1
- package/src/cli/command.mjs +41 -2
- package/src/cli/connect.mjs +2 -29
- package/src/cli/context.mjs +1 -0
- package/src/cli/dream.mjs +93 -69
- package/src/cli/harness.mjs +4 -4
- package/src/cli/help.mjs +1 -1
- package/src/cli/ingest-okf.mjs +4 -4
- package/src/cli/init.mjs +0 -25
- package/src/cli/lessons.mjs +51 -0
- package/src/cli/memory-plugin.mjs +1 -1
- package/src/cli/plugin/copies.mjs +64 -14
- package/src/cli/plugin/create.mjs +6 -7
- package/src/cli/plugin/index.mjs +11 -7
- package/src/cli/plugin/install.mjs +10 -15
- package/src/cli/plugin/remove.mjs +1 -1
- package/src/cli/plugin/search.mjs +2 -2
- package/src/cli/preflight.mjs +19 -6
- package/src/cli/proposals.mjs +1 -1
- package/src/cli/recall.mjs +66 -23
- package/src/cli/release.mjs +4 -1
- package/src/cli/remember.mjs +3 -14
- package/src/cli/server.mjs +18 -0
- package/src/cli/startup.mjs +1 -0
- package/src/cli/task.mjs +9 -2
- package/src/cli/update.mjs +10 -3
- package/src/core/access-ledger.mjs +78 -0
- package/src/core/agent-manager.mjs +2 -2
- package/src/core/automatic-brain-updates.mjs +11 -6
- package/src/core/brain-locate.mjs +15 -0
- package/src/core/brain-updates.mjs +42 -12
- package/src/core/claude-hooks.mjs +3 -1
- package/src/core/cleanup-task.mjs +15 -0
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/cli-maintenance.mjs +4 -1
- package/src/core/cli-semantic-index.mjs +23 -3
- package/src/core/context-compactor.mjs +7 -2
- package/src/core/context-compiler.mjs +26 -1
- package/src/core/context-policy.mjs +7 -3
- package/src/core/daemon-control.mjs +22 -0
- package/src/core/daemon-loop.mjs +39 -62
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/dream.mjs +33 -105
- package/src/core/ensure-dependencies.mjs +148 -0
- package/src/core/fast-recall.mjs +2 -2
- package/src/core/field-evaluation.mjs +23 -0
- package/src/core/gate-mode.mjs +1 -1
- package/src/core/global-brain-updates.mjs +1 -1
- package/src/core/harness-hooks.mjs +14 -2
- package/src/core/lesson-hooks.mjs +74 -0
- package/src/core/lesson-memory.mjs +24 -0
- package/src/core/lesson-sessions.mjs +52 -0
- package/src/core/lessons.mjs +96 -0
- package/src/core/mesh-brain-updates.mjs +2 -1
- package/src/core/meta-harness.mjs +2 -2
- package/src/core/optimizer.mjs +66 -165
- package/src/core/plugin-contracts/card.mjs +3 -0
- package/src/core/plugin-loader.mjs +12 -3
- package/src/core/plugin-runtime.mjs +40 -1
- package/src/core/plugin-store.mjs +35 -23
- package/src/core/post-mortem.mjs +52 -36
- package/src/core/preflight-gate.mjs +23 -2
- package/src/core/proposal-applier.mjs +31 -2
- package/src/core/repo-sync.mjs +4 -6
- package/src/core/required-plugins.mjs +26 -4
- package/src/core/rule-length.mjs +23 -0
- package/src/core/runtime.mjs +1 -1
- package/src/core/scheduler.mjs +35 -43
- package/src/core/schema.mjs +1 -2
- package/src/core/search.mjs +4 -3
- package/src/core/server-restart.mjs +128 -7
- package/src/core/server-service.mjs +148 -0
- package/src/core/session-watcher.mjs +19 -1
- package/src/core/skill-gap.mjs +16 -0
- package/src/core/ssss-kernel-bridge.mjs +33 -6
- package/src/core/startup-health.mjs +6 -1
- package/src/core/surface.mjs +68 -57
- package/src/core/task-context.mjs +9 -8
- package/src/core/task-executors.mjs +54 -106
- package/src/core/validated-write.mjs +5 -0
- package/src/core/vault-files.mjs +8 -2
- package/src/core/vault.mjs +1 -1
- package/src/core/vector-field.mjs +134 -192
- package/src/server/index.mjs +3 -1
- package/src/server/invoke-core-child.mjs +24 -0
- package/src/server/invoke-core-operations.mjs +102 -0
- package/src/server/route-manifest.json +0 -56
- package/src/server/routes/brains.mjs +0 -18
- package/src/server/routes/context.mjs +0 -41
- package/src/server/routes/invoke.mjs +26 -1
- package/src/server/routes/ssss.mjs +0 -169
- package/src/server/routes/system.mjs +0 -15
- package/src/server/routes/tasks.mjs +0 -26
- package/src/server/routes/vault.mjs +2 -23
- package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
- package/src/core/append-log.mjs +0 -131
- package/src/core/plugin-tasks.mjs +0 -173
- package/templates/onboarding-interview.md +0 -82
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
- /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'
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3
|
-
"version": "1.1.
|
|
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
|
|
9
|
-
"version": "1.1.
|
|
8
|
+
"name": "tr-plugin-git",
|
|
9
|
+
"version": "1.1.5",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@gregiteen/ssss-cli": "0.14.
|
|
12
|
+
"@gregiteen/ssss-cli": "0.14.5"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"total-recall-brain": "3.
|
|
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.
|
|
720
|
-
"resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.
|
|
721
|
-
"integrity": "sha512-
|
|
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.
|
|
1892
|
-
"resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.
|
|
1893
|
-
"integrity": "sha512-
|
|
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.
|
|
6537
|
-
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.
|
|
6538
|
-
"integrity": "sha512-
|
|
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.
|
|
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
|
|
3
|
-
"version": "1.1.
|
|
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.
|
|
23
|
+
"@gregiteen/ssss-cli": "0.14.5"
|
|
23
24
|
},
|
|
24
25
|
"overrides": {
|
|
25
|
-
"@ssss/cli": "npm:@gregiteen/ssss-cli@0.14.
|
|
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
|
+
}
|