@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 +48 -0
- package/README.md +124 -5
- package/dist/agent-run.js +1 -2
- package/dist/agents/claude.js +1 -5
- package/dist/brain/cli.js +52 -20
- package/dist/brain/git.js +8 -2
- package/dist/brain/projects.js +1 -1
- package/dist/brain/server.js +27 -6
- package/dist/brain/skills.js +2 -1
- package/dist/brain/store.js +282 -53
- package/dist/brain/todos.js +48 -8
- package/dist/cli.js +3 -12
- package/dist/commands.js +7 -9
- package/dist/config/render-instructions.js +1 -1
- package/dist/config/render-mcp.js +2 -3
- package/dist/config/render-skills.js +3 -2
- package/dist/config-tree.js +0 -22
- package/dist/constants.js +1 -2
- package/dist/guards.js +114 -8
- package/dist/process.js +0 -5
- package/dist/project.js +2 -23
- package/dist/renderer.js +7 -2
- package/dist/templates.js +35 -7
- package/dist/version.js +22 -0
- package/examples/basic-config/agent-config/starter/basic-project/agent-run.jsonc +1 -1
- package/package.json +2 -3
- package/scripts/install-systemd-jobs.sh +1 -8
- /package/examples/basic-config/agent-config/starter/basic-project/{overrides/triage.SKILL.md.njk → skills/triage/SKILL.md.njk} +0 -0
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`,
|
|
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
|
-
|
|
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
|
|
886
|
-
|
|
887
|
-
|
|
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.
|
|
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; } });
|
package/dist/agents/claude.js
CHANGED
|
@@ -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
|
-
:
|
|
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
|
-
|
|
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
|
-
|
|
112
|
+
report(store.search(positionals.join(' ')));
|
|
106
113
|
break;
|
|
107
114
|
case 'context':
|
|
108
|
-
|
|
115
|
+
report(store.context(positionals.join(' ')));
|
|
109
116
|
break;
|
|
110
117
|
case 'get':
|
|
111
|
-
|
|
118
|
+
report(store.get(requireId()));
|
|
112
119
|
break;
|
|
113
120
|
case 'remember':
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
132
|
+
report(store.deprecate(requireId(), requireRevision(), positionals.slice(1).join(' ')));
|
|
123
133
|
break;
|
|
124
134
|
case 'skills':
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
+
report((0, todos_1.listTodos)(store));
|
|
144
176
|
else if (operation === 'get')
|
|
145
|
-
|
|
177
|
+
report((0, todos_1.getTodo)(store, requireId()));
|
|
146
178
|
else if (operation === 'add')
|
|
147
|
-
|
|
179
|
+
report((0, todos_1.addTodo)(store, todos_1.todoInput.parse(json())));
|
|
148
180
|
else if (operation === 'update')
|
|
149
|
-
|
|
181
|
+
report((0, todos_1.updateTodo)(store, requireId(), requireRevision(), todos_1.todoChanges.parse(json())));
|
|
150
182
|
else if (operation === 'complete')
|
|
151
|
-
|
|
183
|
+
report((0, todos_1.updateTodo)(store, requireId(), requireRevision(), { status: 'done' }));
|
|
152
184
|
else if (operation === 'import' || operation === 'sync')
|
|
153
|
-
|
|
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
|
-
|
|
193
|
+
report((0, config_1.listBrainProjects)(root));
|
|
162
194
|
else if (operation === 'info')
|
|
163
|
-
|
|
195
|
+
report({ profile: store.profile, ...(0, projects_1.inspectProject)(cwd) });
|
|
164
196
|
else if (operation === 'bootstrap')
|
|
165
|
-
|
|
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
|
-
|
|
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);
|
package/dist/brain/projects.js
CHANGED
|
@@ -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.',
|
package/dist/brain/server.js
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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));
|
package/dist/brain/skills.js
CHANGED
|
@@ -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 =
|
|
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)
|