@technomoron/agent-run 0.99.31 → 0.99.33

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/CHANGES CHANGED
@@ -1,6 +1,54 @@
1
1
  CHANGES
2
2
  =======
3
3
 
4
+ Version 0.99.33 (2026-09-06)
5
+ ----------------------------
6
+ * Run the agent-brain MCP server as `agent-brain` from `PATH` and choose its
7
+ socket when the bridge starts, so generated agent configuration no longer
8
+ contains the path of whichever install rendered it. Re-render existing
9
+ profiles with `agent-run update`.
10
+ * Let the git guard through for throwaway test repositories: `commit`, `tag`
11
+ and `push` run normally below a directory holding an `.agent-run-test-repo`
12
+ marker, so a project's own test suite works inside an agent session.
13
+ * Match the git guard on the subcommand instead of any argument, so
14
+ `git log --grep push` or a branch named `commit` is no longer blocked.
15
+ * Fix the Windows guard shims: strip a leading PATH entry only when it is the
16
+ guard directory, resolve the real command explicitly, and refuse recursive
17
+ resolution.
18
+ * Archive closed review findings to a compact history file and add
19
+ `review_history` for recalling them without them appearing in normal
20
+ knowledge retrieval.
21
+ * Keep brain retrieval working when a single knowledge or task file is
22
+ malformed, reporting the broken file instead of failing every read.
23
+ * Rebuild the search index only when the underlying files change, so read-only
24
+ retrieval no longer writes on every call.
25
+ * Run brain Git operations against the real git binary rather than the guard
26
+ shim, and stage `templates/` with `sync save`.
27
+ * Render skills through a loader restricted to the skills directories, so a
28
+ skill template cannot include files elsewhere in the configuration root.
29
+ * Omit disabled MCP servers from Codex and Grok configuration, as Claude and
30
+ Gemini already do.
31
+ * Remove `AGENT_WRAPPER_FORCE_PERMISSIVE`, the undocumented environment
32
+ variable that disabled Claude's permission prompts.
33
+ * Stop overwriting systemd units and helper scripts in the configuration root
34
+ on every run.
35
+ * Remove `scripts/release-check.sh`. `pnpm release` now only creates and pushes
36
+ the tag, and `pnpm run release:preflight` is a clean build plus the tests.
37
+
38
+ Version 0.99.32 (2026-09-06)
39
+ ----------------------------
40
+ * Add severity, an assigned C1/H1/M1/L1 label, and an open/fixed/wontfix state
41
+ to review knowledge, with `review_list` and `resolve_review` for working
42
+ through findings in priority order.
43
+ * Add `amend_knowledge` to revise an active item in place, keeping its id, file
44
+ name, and history.
45
+ * Let `remember` choose a file name instead of the item id, with the id appended
46
+ when that name is taken.
47
+ * Read the package version from `package.json` in one place instead of a second
48
+ copy in the source, so the version agent-brain reports cannot drift.
49
+ * Fix task updates resetting every field the caller did not mention, which made
50
+ completing a task discard its description, priority, labels, and notes.
51
+
4
52
  Version 0.99.31 (2026-09-06)
5
53
  ----------------------------
6
54
  * Add agent-brain for shared project knowledge, skills, review context and
package/README.md CHANGED
@@ -206,6 +206,23 @@ Codex and Grok and are omitted from Claude and Gemini.
206
206
  `local.md.njk` and `overrides/` are not created unless they contain actual
207
207
  profile-specific configuration.
208
208
 
209
+ ## Guard Shims
210
+
211
+ `blockGitWrite`, `blockPublish`, and `blockGithubRelease` put small `git`, `npm`,
212
+ `pnpm`, and `gh` shims on the launched agent's `PATH`. They refuse
213
+ `git commit`, `git tag`, `git push`, `npm publish`, `pnpm publish`, and
214
+ `gh release create` with exit code 42 and pass everything else through to the
215
+ real command. Set `AGENT_RUN_ALLOW_GIT_WRITE=1`, `AGENT_RUN_ALLOW_PUBLISH=1`, or
216
+ `AGENT_RUN_ALLOW_GITHUB_RELEASE=1` for a single invocation you have reviewed.
217
+
218
+ Throwaway repositories that a test suite creates and deletes are exempt from the
219
+ `git` shim. Create an empty `.agent-run-test-repo` file in the repository, or in
220
+ any directory above it, and `git commit`, `git tag`, and `git push` run normally
221
+ for anything below that marker. The shim reads the marker from the directory
222
+ `git` would work in, following any leading `-C` options. This is what lets
223
+ `pnpm test` run inside an agent session; nothing outside a marked directory is
224
+ affected.
225
+
209
226
  ## Profile Resolution
210
227
 
211
228
  The mapped path is:
@@ -597,11 +614,25 @@ and restore the previous unconfigured-project behavior.
597
614
  reports omitted items. Use `get_knowledge` to read complete items when necessary.
598
615
  Always-recalled knowledge is considered first, then matching file patterns,
599
616
  then lexical results. Search uses FTS ranking with extra weight for titles and tags.
617
+ Queries compare the current files with the index and write only added, changed,
618
+ or removed entries. Unchanged queries do not take a database write lock. Context
619
+ retrieval scans the files once. Search and context MCP tools declare that they
620
+ may write because they can refresh this derived index.
600
621
  Symbols contribute search terms. Manual Markdown changes are read on every query;
601
622
  the index can be deleted while the service is stopped and rebuilt on the next query.
602
623
 
603
624
  ### Knowledge and skills
604
625
 
626
+ Malformed knowledge and task files do not block other entries. When a scan finds
627
+ problems, MCP and CLI list/search responses contain `items` and `brokenFiles`;
628
+ object responses, including context, gain a `brokenFiles` field. Each error lists
629
+ the source path, `status: "broken"`, a reason, and an ID when it can be parsed.
630
+ Responses without errors keep their existing format. Broken entries are excluded
631
+ from search and context, and reads or updates of those entries are refused.
632
+ Correct the file on disk; the next scan picks up the repair automatically.
633
+ Duplicate IDs flag all matching files. External task imports and new review
634
+ numbering stop when broken files would make deduplication or numbering unreliable.
635
+
605
636
  Knowledge is one Markdown file per item, with YAML metadata:
606
637
 
607
638
  ```markdown
@@ -641,7 +672,8 @@ install either. Global skills remain available without copying them into each
641
672
  project. The brain-memory skill explains these choices to native agents.
642
673
 
643
674
  Available MCP tools include `get_context`, `search_knowledge`, `get_knowledge`,
644
- `remember`, `promote`, `deprecate_knowledge`, and `review_context`.
675
+ `remember`, `amend_knowledge`, `promote`, `deprecate_knowledge`, `review_context`,
676
+ `review_list`, `resolve_review`, `review_history`, and `review_archive`.
645
677
  Writes use atomic file replacement and a shared lock; concurrent writes either
646
678
  complete or report that the caller should retry. After a crashed writer, stop
647
679
  the service, verify the PID in `runtime/brain-write.lock` is no longer running,
@@ -651,10 +683,71 @@ and remove that lock before restarting.
651
683
  agent-brain remember --json '{"scope":"project","type":"observation","title":"Refresh handling","content":"Investigate concurrent refresh requests."}'
652
684
  agent-brain search "refresh"
653
685
  agent-brain get ITEM_ID
686
+ agent-brain amend ITEM_ID --revision REVISION --json '{"content":"Corrected text."}'
654
687
  agent-brain deprecate ITEM_ID --revision REVISION "No longer applies"
655
688
  agent-brain promote ITEM_ID --scope global --revision REVISION --confirmed
656
689
  ```
657
690
 
691
+ `amend_knowledge` revises an active item in place, keeping its id, file name, scope,
692
+ type, authority, and history, and updating only the fields you pass. Use it to correct
693
+ or extend knowledge instead of writing a near-duplicate. Scope, type, authority, and
694
+ review severity are not amendable; if one of those is wrong, deprecate the item and
695
+ write a corrected one.
696
+
697
+ #### File names
698
+
699
+ `remember` names each file after the item id. Pass `filename` to choose the name
700
+ instead, as lower-case words separated by single dashes and no extension:
701
+
702
+ ```sh
703
+ agent-brain remember --json '{"scope":"project","type":"observation","title":"Build flow","content":"...","filename":"build-flow"}'
704
+ ```
705
+
706
+ If that name is taken, the item id is appended rather than overwriting anything. The
707
+ name is storage only and is never written into the metadata: the `id` in the front
708
+ matter is the identity, so files stay safe to rename by hand and the item is still found
709
+ by id afterwards.
710
+
711
+ #### Review findings
712
+
713
+ Review knowledge is a numbered, prioritized list. `remember` requires a
714
+ `severity` of `critical`, `high`, `medium`, or `low` when the type is `review`,
715
+ and rejects `severity` for every other type. The store assigns the finding
716
+ label itself: `C1`, `H1`, `M1`, `L1`, counting up per severity within the scope.
717
+ Resolved numbers are never handed out again, so a label always refers to the
718
+ same finding.
719
+
720
+ Each finding also carries a `state` of `open`, `fixed`, or `wontfix`, separate
721
+ from the `active` and `deprecated` status that controls recall. New findings
722
+ start `open`.
723
+
724
+ ```sh
725
+ agent-brain review list
726
+ agent-brain review list --severity high --state open
727
+ agent-brain review resolve ITEM_ID --revision REVISION --state fixed "Fixed by using a stable command name"
728
+ ```
729
+
730
+ `review_list` returns findings in severity then number order, open ones only
731
+ unless states are given, each with the revision needed to resolve it.
732
+ `resolve_review` records the state and reason and takes the finding out of active
733
+ recall. It writes a short record (label, title, outcome, date, reason, and original
734
+ file/revision) to the scope's `review-history.jsonl`, then deletes the full finding
735
+ file. `review-counters.json` preserves numbering. These files participate in brain
736
+ Git sync but are excluded from normal knowledge retrieval.
737
+
738
+ Use MCP `review_history` to recall old fixes, with optional `query`, `scope`, `state`,
739
+ `offset`, and `limit` (default 100). The CLI equivalent is
740
+ `agent-brain review history [query]`. Git retains full details only when the original
741
+ files were committed before cleanup; the MCP history is sufficient for the short fix list.
742
+ Use `review_archive` with a scope, or `agent-brain review archive --scope project`,
743
+ to compact existing files already marked `fixed` or `wontfix`. Deferred findings
744
+ remain open. Mark a finding `wontfix` only when the user explicitly drops it or
745
+ confirms it is intentional. A request to postpone an issue does not close it.
746
+
747
+ Finding labels are indexed, so `search_knowledge` for `H2` finds that finding.
748
+ Review files written before these fields existed keep parsing; they simply carry
749
+ no label and stay out of the finding list.
750
+
658
751
  Remembered deductions default to inferred authority and must remain observations,
659
752
  memories, or review records. Global writes require explicit user authority.
660
753
  Promotion copies an item, records its origin, preserves the original, and requires
@@ -668,6 +761,13 @@ A project/default skill overrides a global skill with the same name. Add
668
761
  `extends: global:NAME` to include the global skill's instructions before the local
669
762
  instructions. The existing native skill-template system continues to work.
670
763
 
764
+ Skill templates and their includes must stay under `global/skills/` or the active
765
+ project/default profile's `skills/` directory. Use config-root-relative paths such
766
+ as `{% include "global/skills/shared/checks.md" %}`, or `./` paths relative to the
767
+ including template. Nunjucks variables remain available. Symlinks are rejected,
768
+ and each file is limited to 1 MiB. Move snippets and skill overrides stored
769
+ elsewhere into these directories before rendering or loading those skills.
770
+
671
771
  The installed `brain-memory`, `brain-review`, and `todo-manager` skills describe
672
772
  retrieval, persistence, review learning, and task workflows. Review records use
673
773
  the knowledge tools and remain inferred unless confirmed by the user.
@@ -739,9 +839,14 @@ agent-brain sync push --confirmed
739
839
 
740
840
  The command without an action previews Git status and eligible files. Saving
741
841
  includes Markdown knowledge, skills, tasks, project metadata, and `.gitignore`.
842
+ It also includes all regular files under each scope's `templates/` directory,
843
+ including nested Nunjucks templates and supporting files. Template additions,
844
+ edits, and deletions are synced; symlinks are rejected.
742
845
  It excludes indexes, runtime state, secrets, `brain.jsonc`, and native agent
743
846
  configuration. Existing staged changes block a save. Configure the remote and
744
847
  upstream with normal Git commands using your existing credentials.
848
+ Brain Git operations use `AGENT_RUN_REAL_PATH` when available, so agent-session
849
+ Git guards do not intercept configuration sync.
745
850
  After cloning onto another machine, run `agent-brain init` to restore generated
746
851
  profile markers and shared templates, then update source roots in `config.yaml`
747
852
  to match that machine.
@@ -765,7 +870,13 @@ It listens on `$XDG_RUNTIME_DIR/agent-brain.sock`, or
765
870
  directory must belong to the current user and have mode `0700`; the socket has
766
871
  mode `0600`. No TCP listener or application user database is created.
767
872
 
768
- `agent-run mcp` bridges native-agent stdio to this socket. Use `--socket PATH`
873
+ Generated native-agent configurations run `agent-brain mcp` through `PATH`, so
874
+ `agent-brain` must be installed and available there. The bridge selects the socket
875
+ when it starts; generated files do not depend on the installation or runtime
876
+ directory used to render them. Re-render existing profiles with `agent-run update`
877
+ to replace older commands that contain absolute installation paths.
878
+
879
+ `agent-run mcp` also bridges native-agent stdio to this socket. Use `--socket PATH`
769
880
  for a particular service. The bridge verifies the socket's ownership and private
770
881
  permissions. A missing or unreachable socket is an error; start the service
771
882
  before connecting. There is no standalone stdio server or TCP fallback. Apicore
@@ -812,6 +923,9 @@ Transport and connector implementations follow the
812
923
  ## Systemd Jobs
813
924
 
814
925
  The repo includes optional systemd units for keeping global AI tooling current.
926
+ Run the installer from the agent-run checkout. Setup, generation, and updates do
927
+ not copy systemd units or helper scripts into `.agent-run`. Remove legacy copies
928
+ there once any custom changes have been moved to your maintained scripts.
815
929
 
816
930
  Install the global AI tools updater:
817
931
 
@@ -882,6 +996,11 @@ After updating `package.json` and `CHANGES`, run:
882
996
  pnpm release
883
997
  ```
884
998
 
885
- This validates the repo, creates the annotated tag matching the package version,
886
- and pushes that tag to `origin`. The tag push triggers the release workflow,
887
- which verifies, packs, publishes to npm, and creates the GitHub release.
999
+ This creates the annotated tag matching the package version and pushes that tag
1000
+ to `origin`. It stops if the tag already exists, and removes the local tag again
1001
+ if the push fails. The tag push triggers the release workflow, which runs the
1002
+ tests and build, packs, publishes to npm, and creates the GitHub release.
1003
+
1004
+ `pnpm run release:preflight` is the local gate to run first: a clean build and
1005
+ the full test suite. Update `package.json` and `CHANGES` yourself; nothing
1006
+ checks them for you.
package/dist/agent-run.js CHANGED
@@ -1,14 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.parseEditorCommand = exports.resolveProfile = exports.resolveAgentDir = exports.findProjectRoot = exports.defaultConfigRootSearchCandidates = exports.defaultConfigRoot = exports.parseInvocation = void 0;
4
+ exports.parseEditorCommand = exports.resolveProfile = exports.resolveAgentDir = exports.findProjectRoot = exports.defaultConfigRoot = exports.parseInvocation = void 0;
5
5
  const path = require("path");
6
6
  const commands_1 = require("./commands");
7
7
  var cli_1 = require("./cli");
8
8
  Object.defineProperty(exports, "parseInvocation", { enumerable: true, get: function () { return cli_1.parseInvocation; } });
9
9
  var project_1 = require("./project");
10
10
  Object.defineProperty(exports, "defaultConfigRoot", { enumerable: true, get: function () { return project_1.defaultConfigRoot; } });
11
- Object.defineProperty(exports, "defaultConfigRootSearchCandidates", { enumerable: true, get: function () { return project_1.defaultConfigRootSearchCandidates; } });
12
11
  Object.defineProperty(exports, "findProjectRoot", { enumerable: true, get: function () { return project_1.findProjectRoot; } });
13
12
  Object.defineProperty(exports, "resolveAgentDir", { enumerable: true, get: function () { return project_1.resolveAgentDir; } });
14
13
  Object.defineProperty(exports, "resolveProfile", { enumerable: true, get: function () { return project_1.resolveProfile; } });
@@ -51,13 +51,9 @@ exports.claudeAdapter = {
51
51
  generate,
52
52
  spawn(runtime, options) {
53
53
  (0, shared_1.assertRuntimeFile)('claude', runtime.instructionFile, runtime.context.profileDir);
54
- const forcedPermissionArgs = process.env.AGENT_WRAPPER_FORCE_PERMISSIVE === '1' &&
55
- typeof process.getuid === 'function' && process.getuid() !== 0
56
- ? ['--permission-mode', 'bypassPermissions']
57
- : [];
58
54
  const runtimeArgs = options.wrapperArgs.sandboxMode === 'danger'
59
55
  ? ['--dangerously-skip-permissions']
60
- : forcedPermissionArgs;
56
+ : [];
61
57
  return {
62
58
  command: options.binary,
63
59
  args: [
package/dist/brain/cli.js CHANGED
@@ -8,6 +8,7 @@ const constants_1 = require("../constants");
8
8
  const project_1 = require("../project");
9
9
  const config_1 = require("./config");
10
10
  const projects_1 = require("./projects");
11
+ const zod_1 = require("zod");
11
12
  const store_1 = require("./store");
12
13
  const skills_1 = require("./skills");
13
14
  const todos_1 = require("./todos");
@@ -18,7 +19,7 @@ async function brainMain(argv, wrapperCommand) {
18
19
  configdir: { type: 'string' }, cwd: { type: 'string' }, socket: { type: 'string' },
19
20
  quick: { type: 'boolean' }, guided: { type: 'boolean' },
20
21
  pull: { type: 'boolean' },
21
- json: { type: 'string' }, scope: { type: 'string' }, revision: { type: 'string' },
22
+ json: { type: 'string' }, scope: { type: 'string' }, revision: { type: 'string' }, state: { type: 'string' }, severity: { type: 'string' },
22
23
  confirmed: { type: 'boolean' }, message: { type: 'string' }, help: { type: 'boolean', short: 'h' }
23
24
  } });
24
25
  if (values.configdir)
@@ -33,9 +34,14 @@ async function brainMain(argv, wrapperCommand) {
33
34
  process.stdout.write([
34
35
  'agent-brain init | status | search <query> | context <task> | get <id>',
35
36
  'agent-brain remember --json <knowledge-object>',
37
+ 'agent-brain amend <id> --revision <hash> --json <changes>',
36
38
  'agent-brain promote <id> --scope <scope> --revision <hash> --confirmed',
37
39
  'agent-brain deprecate <id> --revision <hash> [reason]',
38
40
  'agent-brain skills [name]',
41
+ 'agent-brain review list [--severity <level>] [--state <state>]',
42
+ 'agent-brain review history [query] [--scope <scope>] [--state fixed|wontfix]',
43
+ 'agent-brain review archive --scope <scope>',
44
+ 'agent-brain review resolve <id> --revision <hash> --state fixed|wontfix [reason]',
39
45
  'agent-brain todo list | get <id> | add --json <task-object>',
40
46
  'agent-brain todo update <id> --revision <hash> --json <changes>',
41
47
  'agent-brain todo complete <id> --revision <hash>',
@@ -96,61 +102,87 @@ async function brainMain(argv, wrapperCommand) {
96
102
  throw new Error('--revision is required; read the item first'); return values.revision; };
97
103
  const json = () => { if (!values.json)
98
104
  throw new Error('--json is required'); return JSON.parse(values.json); };
105
+ const report = (value) => print(store.withDiagnostics(value));
99
106
  try {
100
107
  switch (command) {
101
108
  case 'status':
102
- print({ configRoot: root, enabled: (0, config_1.readBrainConfig)(root)?.enabled ?? false, profile: store.profile, scopes: store.scopes, knowledge: store.allKnowledge().length, todos: (0, todos_1.listTodos)(store).length });
109
+ report({ configRoot: root, enabled: (0, config_1.readBrainConfig)(root)?.enabled ?? false, profile: store.profile, scopes: store.scopes, knowledge: store.allKnowledge().length, todos: (0, todos_1.listTodos)(store).length });
103
110
  break;
104
111
  case 'search':
105
- print(store.search(positionals.join(' ')));
112
+ report(store.search(positionals.join(' ')));
106
113
  break;
107
114
  case 'context':
108
- print(store.context(positionals.join(' ')));
115
+ report(store.context(positionals.join(' ')));
109
116
  break;
110
117
  case 'get':
111
- print(store.get(requireId()));
118
+ report(store.get(requireId()));
112
119
  break;
113
120
  case 'remember':
114
- print(store.remember(store_1.knowledgeInput.parse(json())));
121
+ report(store.remember(store_1.knowledgeInput.parse(json())));
115
122
  break;
116
123
  case 'promote':
117
124
  if (!values.confirmed)
118
125
  throw new Error('Promotion requires --confirmed after explicit user approval');
119
- print(store.promote(requireId(), store_1.scopeSchema.parse(values.scope), requireRevision()));
126
+ report(store.promote(requireId(), store_1.scopeSchema.parse(values.scope), requireRevision()));
127
+ break;
128
+ case 'amend':
129
+ report(store.amend(requireId(), requireRevision(), store_1.knowledgeChanges.parse(json())));
120
130
  break;
121
131
  case 'deprecate':
122
- print(store.deprecate(requireId(), requireRevision(), positionals.slice(1).join(' ')));
132
+ report(store.deprecate(requireId(), requireRevision(), positionals.slice(1).join(' ')));
123
133
  break;
124
134
  case 'skills':
125
- print(positionals[0] ? (0, skills_1.getSkill)(store, positionals[0]) : (0, skills_1.listSkills)(store));
135
+ report(positionals[0] ? (0, skills_1.getSkill)(store, positionals[0]) : (0, skills_1.listSkills)(store));
136
+ break;
137
+ case 'review': {
138
+ const operation = positionals.shift() ?? 'list';
139
+ if (operation === 'list') {
140
+ report(store.reviews({
141
+ ...(values.severity ? { severity: [store_1.severitySchema.parse(values.severity)] } : {}),
142
+ ...(values.state ? { state: [store_1.reviewStateSchema.parse(values.state)] } : {})
143
+ }).map((item) => ({ finding: item.finding, severity: item.severity, state: item.state ?? 'open', title: item.title, id: item.id, revision: item.revision })));
144
+ }
145
+ else if (operation === 'history') {
146
+ report(store.reviewHistory({ query: positionals.join(' '), ...(values.scope ? { scope: store_1.scopeSchema.parse(values.scope) } : {}), ...(values.state ? { state: zod_1.z.enum(['fixed', 'wontfix']).parse(values.state) } : {}), limit: Number.MAX_SAFE_INTEGER }));
147
+ }
148
+ else if (operation === 'archive') {
149
+ report(store.archiveReviews(store_1.scopeSchema.parse(values.scope)));
150
+ }
151
+ else if (operation === 'resolve') {
152
+ const state = zod_1.z.enum(['fixed', 'wontfix']).parse(values.state);
153
+ report(store.resolveReview(requireId(), requireRevision(), state, positionals.slice(1).join(' ') || undefined));
154
+ }
155
+ else
156
+ throw new Error('Usage: agent-brain review list | resolve <id> --revision <hash> --state fixed|wontfix');
126
157
  break;
158
+ }
127
159
  case 'sync': {
128
160
  const action = positionals[0];
129
161
  if (!action) {
130
- print((0, git_1.gitPreview)(store));
162
+ report((0, git_1.gitPreview)(store));
131
163
  break;
132
164
  }
133
165
  if (!['init', 'save', 'pull', 'push'].includes(action))
134
166
  throw new Error('Unknown sync action');
135
167
  if (!values.confirmed)
136
168
  throw new Error('Review agent-brain sync output and obtain authorization before using --confirmed');
137
- print((0, git_1.syncGit)(store, action, values.message));
169
+ report((0, git_1.syncGit)(store, action, values.message));
138
170
  break;
139
171
  }
140
172
  case 'todo': {
141
173
  const operation = positionals.shift();
142
174
  if (operation === 'list')
143
- print((0, todos_1.listTodos)(store));
175
+ report((0, todos_1.listTodos)(store));
144
176
  else if (operation === 'get')
145
- print((0, todos_1.getTodo)(store, requireId()));
177
+ report((0, todos_1.getTodo)(store, requireId()));
146
178
  else if (operation === 'add')
147
- print((0, todos_1.addTodo)(store, todos_1.todoInput.parse(json())));
179
+ report((0, todos_1.addTodo)(store, todos_1.todoInput.parse(json())));
148
180
  else if (operation === 'update')
149
- print((0, todos_1.updateTodo)(store, requireId(), requireRevision(), todos_1.todoChanges.parse(json())));
181
+ report((0, todos_1.updateTodo)(store, requireId(), requireRevision(), todos_1.todoChanges.parse(json())));
150
182
  else if (operation === 'complete')
151
- print((0, todos_1.updateTodo)(store, requireId(), requireRevision(), { status: 'done' }));
183
+ report((0, todos_1.updateTodo)(store, requireId(), requireRevision(), { status: 'done' }));
152
184
  else if (operation === 'import' || operation === 'sync')
153
- print(await (0, connectors_1.syncConnector)(store, requireId(), store_1.scopeSchema.parse(values.scope)));
185
+ report(await (0, connectors_1.syncConnector)(store, requireId(), store_1.scopeSchema.parse(values.scope)));
154
186
  else
155
187
  throw new Error('Unknown todo command; use agent-brain --help');
156
188
  break;
@@ -158,11 +190,11 @@ async function brainMain(argv, wrapperCommand) {
158
190
  case 'project': {
159
191
  const operation = positionals[0];
160
192
  if (operation === 'list')
161
- print((0, config_1.listBrainProjects)(root));
193
+ report((0, config_1.listBrainProjects)(root));
162
194
  else if (operation === 'info')
163
- print({ profile: store.profile, ...(0, projects_1.inspectProject)(cwd) });
195
+ report({ profile: store.profile, ...(0, projects_1.inspectProject)(cwd) });
164
196
  else if (operation === 'bootstrap')
165
- print({ status: 'proposal', message: 'Inspect the listed documents to propose architecture, constraints, conventions, and specs. Confirm proposals before saving authoritative knowledge.', ...(0, projects_1.inspectProject)(cwd) });
197
+ report({ status: 'proposal', message: 'Inspect the listed documents to propose architecture, constraints, conventions, and specs. Confirm proposals before saving authoritative knowledge.', ...(0, projects_1.inspectProject)(cwd) });
166
198
  else
167
199
  throw new Error('Usage: agent-run project list | info | bootstrap');
168
200
  break;
package/dist/brain/git.js CHANGED
@@ -11,7 +11,7 @@ const store_1 = require("./store");
11
11
  const config_1 = require("./config");
12
12
  function git(root, args) {
13
13
  const result = (0, node_child_process_1.spawnSync)('git', ['-C', root, ...args], { encoding: 'utf8', shell: false, timeout: 60000,
14
- env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }, maxBuffer: 16 * 1024 * 1024 });
14
+ env: { ...process.env, PATH: process.env.AGENT_RUN_REAL_PATH ?? process.env.PATH, GIT_TERMINAL_PROMPT: '0' }, maxBuffer: 16 * 1024 * 1024 });
15
15
  if (result.error || result.status !== 0)
16
16
  throw new Error(`git ${args[0]} failed: ${result.error?.message ?? result.stderr.trim()}`);
17
17
  return result.stdout.trimEnd();
@@ -40,12 +40,18 @@ function syncableFiles(store) {
40
40
  for (const root of roots) {
41
41
  for (const folder of folders)
42
42
  files.push(...store.files(path.join(root, folder)));
43
- for (const name of ['config.yaml', 'project.md']) {
43
+ files.push(...store.files(path.join(root, 'templates'), { includeAllFiles: true }));
44
+ for (const name of ['config.yaml', 'project.md', 'review-history.jsonl', 'review-counters.json']) {
44
45
  const file = store.safePath(root, name);
45
46
  if (fs.existsSync(file))
46
47
  files.push(file);
47
48
  }
48
49
  }
50
+ if (fs.existsSync(path.join(store.configRoot, '.git'))) {
51
+ const removable = roots.flatMap((root) => ['templates', 'reviews'].map((folder) => path.relative(store.configRoot, path.join(root, folder))));
52
+ const deleted = git(store.configRoot, ['ls-files', '--deleted', '-z', '--', ...removable]).split('\0').filter(Boolean);
53
+ files.push(...deleted.map((file) => store.safePath(file)));
54
+ }
49
55
  const ignore = store.safePath('.gitignore');
50
56
  if (fs.existsSync(ignore))
51
57
  files.push(ignore);
@@ -21,7 +21,7 @@ const builtInSkills = {
21
21
  },
22
22
  'brain-review': {
23
23
  description: 'Review changes against project constraints and previous findings, and save confirmed review knowledge.',
24
- content: 'Call review_context with the task and changed paths. Check correctness, security, architecture, and meaningful test coverage against relevant constraints. Report concrete, actionable findings with severity and file references. If authorized to save the review, call remember with type=review. Keep suspected hazards marked inferred. Never convert a finding into an authoritative constraint without user confirmation.'
24
+ content: 'Call review_context with the task and changed paths. Check correctness, security, architecture, and meaningful test coverage against relevant constraints. Report concrete, actionable findings with severity and file references. If authorized to save the review, call remember with type=review. Keep suspected hazards marked inferred. Never convert a finding into an authoritative constraint without user confirmation. Use review_list for open findings. After completing an authorized fix and relevant checks, resolve_review as fixed with a short explanation and verification result; use wontfix only for an explicit user decision to drop the finding. Postponed findings stay open. Closing saves compact history and removes the full finding file. Recall old fixes through review_history, not normal knowledge search. Use review_archive to compact already closed files when cleanup is requested. Do not create separate done lists or full archived review copies.'
25
25
  },
26
26
  'todo-manager': {
27
27
  description: 'Manage local tasks and import externally tracked tasks without duplicates.',
@@ -13,7 +13,7 @@ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamable
13
13
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
14
14
  const apicore_server_1 = require("@technomoron/apicore-server");
15
15
  const zod_1 = require("zod");
16
- const constants_1 = require("../constants");
16
+ const version_1 = require("../version");
17
17
  const store_1 = require("./store");
18
18
  const skills_1 = require("./skills");
19
19
  const todos_1 = require("./todos");
@@ -22,13 +22,15 @@ const git_1 = require("./git");
22
22
  var config_1 = require("./config");
23
23
  Object.defineProperty(exports, "defaultSocketPath", { enumerable: true, get: function () { return config_1.defaultSocketPath; } });
24
24
  function createBrainServer(store) {
25
- const server = new mcp_js_1.McpServer({ name: 'agent-brain', version: constants_1.PACKAGE_VERSION });
25
+ const server = new mcp_js_1.McpServer({ name: 'agent-brain', version: (0, version_1.packageVersion)() });
26
26
  function tool(name, description, shape, readOnly, handler) {
27
27
  const schema = zod_1.z.object(shape).strict();
28
28
  server.registerTool(name, { description, inputSchema: schema, annotations: { readOnlyHint: readOnly, destructiveHint: false, openWorldHint: false } }, async (input) => {
29
+ store.knowledgeErrors.length = 0;
30
+ store.todoErrors.length = 0;
29
31
  try {
30
32
  const result = await handler(zod_1.z.object(shape).strict().parse(input));
31
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
33
+ return { content: [{ type: 'text', text: JSON.stringify(store.withDiagnostics(result)) }] };
32
34
  }
33
35
  catch (error) {
34
36
  return { isError: true, content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }] };
@@ -52,13 +54,16 @@ function createBrainServer(store) {
52
54
  });
53
55
  tool('get_context', 'Retrieve relevant constraints and knowledge before substantial work. Check omitted; explicitly fetch required knowledge if the budget excluded it.', {
54
56
  task: zod_1.z.string().max(10000), files: zod_1.z.array(zod_1.z.string()).max(100).optional(), symbols: zod_1.z.array(zod_1.z.string()).max(100).optional()
55
- }, true, ({ task, files, symbols }) => store.context(task, files, symbols));
57
+ }, false, ({ task, files, symbols }) => store.context(task, files, symbols));
56
58
  tool('search_knowledge', 'Search Markdown knowledge in the active scopes using SQLite FTS.', {
57
59
  query: zod_1.z.string().max(10000), scopes: zod_1.z.array(store_1.scopeSchema).optional(), types: zod_1.z.array(store_1.typeSchema).optional(),
58
60
  includeDeprecated: zod_1.z.boolean().optional(), limit: zod_1.z.number().int().min(1).max(100).optional()
59
- }, true, ({ query, ...options }) => store.search(query, options));
61
+ }, false, ({ query, ...options }) => store.search(query, options));
60
62
  tool('get_knowledge', 'Read a complete knowledge item and its current revision.', { id }, true, ({ id }) => store.get(id));
61
63
  tool('remember', 'Persist durable knowledge when authorized by the user. Choose scope and a singular type; the server stores it in the matching directory (for example constraint in constraints/, preference in preferences/). Explicit user instructions have authority=user; deductions remain inferred observations. Global writes require explicit global intent. Use todo tools for tasks; skills and templates are separate files.', store_1.knowledgeInput.shape, false, (input) => store.remember(input));
64
+ tool('amend_knowledge', 'Revise an active item in place using its last-read revision, keeping its id, file name, scope, type, authority, and history. Use this to correct or extend existing knowledge instead of writing a near-duplicate. Scope, type, authority, and review severity cannot be changed this way.', {
65
+ id, revision, changes: store_1.knowledgeChanges
66
+ }, false, ({ id, revision, changes }) => store.amend(id, revision, changes));
62
67
  tool('promote', 'Copy active knowledge to another available scope with user authority. Requires explicit user approval; the original is preserved.', {
63
68
  id, scope: store_1.scopeSchema, revision, confirmed: zod_1.z.literal(true)
64
69
  }, false, ({ id, scope, revision }) => store.promote(id, scope, revision));
@@ -67,7 +72,23 @@ function createBrainServer(store) {
67
72
  tool('get_skill', 'Load a full skill only when it applies to the current task.', { name: zod_1.z.string().max(100) }, true, ({ name }) => (0, skills_1.getSkill)(store, name));
68
73
  tool('review_context', 'Retrieve constraints, decisions, prior review findings, and path-specific knowledge for a code review.', {
69
74
  task: zod_1.z.string().max(10000), files: zod_1.z.array(zod_1.z.string()).max(100).optional()
70
- }, true, ({ task, files }) => store.context(`review constraint decision ${task}`, files));
75
+ }, false, ({ task, files }) => store.context(`review constraint decision ${task}`, files));
76
+ tool('review_list', 'List review findings in severity then number order. Open findings only unless states are given; archived findings are in review_history. Each finding carries its label, severity, state, and the revision needed to resolve it.', {
77
+ severity: zod_1.z.array(store_1.severitySchema).optional(), state: zod_1.z.array(store_1.reviewStateSchema).optional()
78
+ }, true, ({ severity, state }) => store.reviews({ severity, state }).map((item) => ({
79
+ finding: item.finding, severity: item.severity, state: item.state ?? 'open', title: item.title,
80
+ applies_to: item.applies_to, id: item.id, revision: item.revision
81
+ })));
82
+ tool('review_history', 'Recall compact closed review records by label, ID, title, or reason. Excluded from normal knowledge retrieval; newest first, with pagination.', {
83
+ scope: store_1.scopeSchema.optional(), query: zod_1.z.string().max(1000).optional(), state: zod_1.z.enum(['fixed', 'wontfix']).optional(),
84
+ offset: zod_1.z.number().int().min(0).optional(), limit: zod_1.z.number().int().min(1).max(1000).optional()
85
+ }, true, (options) => store.reviewHistory(options));
86
+ tool('review_archive', 'Compact already finished review files in one scope into closure history and remove their full files. Leaves unresolved and deferred findings alone.', {
87
+ scope: store_1.scopeSchema
88
+ }, false, ({ scope }) => store.archiveReviews(scope));
89
+ tool('resolve_review', 'Close a review finding as fixed or wontfix, preserving a compact closure record and deleting the full finding file. Supply the revision you last read. Use wontfix only when the user has said the finding is intentional.', {
90
+ id, revision, state: zod_1.z.enum(['fixed', 'wontfix']), reason: zod_1.z.string().max(2000).optional()
91
+ }, false, ({ id, revision, state, reason }) => store.resolveReview(id, revision, state, reason));
71
92
  tool('todo_list', 'List structured tasks in the active scopes.', { status: todos_1.todoInput.shape.status.optional() }, true, ({ status }) => (0, todos_1.listTodos)(store).filter((item) => !status || item.status === status));
72
93
  tool('todo_get', 'Read a task and its revision.', { id }, true, ({ id }) => (0, todos_1.getTodo)(store, id));
73
94
  tool('todo_add', 'Create a local task. Reimporting the same external task returns the existing task.', todos_1.todoInput.shape, false, (input) => (0, todos_1.addTodo)(store, input));
@@ -19,11 +19,12 @@ function getSkills(store) {
19
19
  const directory = store.scopeDirectory(store.profile ? 'project' : 'default');
20
20
  const env = (0, templates_1.createNunjucksEnv)(store.configRoot);
21
21
  const context = (0, templates_1.buildRenderContext)(store.projectRoot, directory, store.configRoot, (0, manifest_1.normalizeManifest)((0, manifest_1.loadManifest)(store.configRoot, directory, profile), profile), env);
22
+ const skillEnv = (0, templates_1.createSkillNunjucksEnv)(store.configRoot, directory);
22
23
  const skills = new Map();
23
24
  const globalSkills = new Map();
24
25
  for (const { scope, directory } of store.scopes) {
25
26
  for (const file of store.files(path.join(directory, 'skills')).filter((file) => path.basename(file) === 'SKILL.md')) {
26
- const text = env.renderString(store.read(file), context);
27
+ const text = skillEnv.render(path.relative(store.configRoot, file), context);
27
28
  const document = (0, store_1.readMarkdown)(text);
28
29
  const parsed = metadata.parse(document.metadata);
29
30
  if (path.basename(path.dirname(file)) !== parsed.name)