@technomoron/agent-run 0.99.31 → 0.99.32
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 +14 -0
- package/README.md +52 -1
- package/dist/brain/cli.js +24 -1
- package/dist/brain/server.js +14 -2
- package/dist/brain/store.js +90 -10
- package/dist/brain/todos.js +12 -1
- package/dist/cli.js +3 -12
- package/dist/constants.js +1 -2
- package/dist/version.js +22 -0
- package/package.json +1 -1
package/CHANGES
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
CHANGES
|
|
2
2
|
=======
|
|
3
3
|
|
|
4
|
+
Version 0.99.32 (2026-09-06)
|
|
5
|
+
----------------------------
|
|
6
|
+
* Add severity, an assigned C1/H1/M1/L1 label, and an open/fixed/wontfix state
|
|
7
|
+
to review knowledge, with `review_list` and `resolve_review` for working
|
|
8
|
+
through findings in priority order.
|
|
9
|
+
* Add `amend_knowledge` to revise an active item in place, keeping its id, file
|
|
10
|
+
name, and history.
|
|
11
|
+
* Let `remember` choose a file name instead of the item id, with the id appended
|
|
12
|
+
when that name is taken.
|
|
13
|
+
* Read the package version from `package.json` in one place instead of a second
|
|
14
|
+
copy in the source, so the version agent-brain reports cannot drift.
|
|
15
|
+
* Fix task updates resetting every field the caller did not mention, which made
|
|
16
|
+
completing a task discard its description, priority, labels, and notes.
|
|
17
|
+
|
|
4
18
|
Version 0.99.31 (2026-09-06)
|
|
5
19
|
----------------------------
|
|
6
20
|
* Add agent-brain for shared project knowledge, skills, review context and
|
package/README.md
CHANGED
|
@@ -641,7 +641,8 @@ install either. Global skills remain available without copying them into each
|
|
|
641
641
|
project. The brain-memory skill explains these choices to native agents.
|
|
642
642
|
|
|
643
643
|
Available MCP tools include `get_context`, `search_knowledge`, `get_knowledge`,
|
|
644
|
-
`remember`, `promote`, `deprecate_knowledge`,
|
|
644
|
+
`remember`, `amend_knowledge`, `promote`, `deprecate_knowledge`, `review_context`,
|
|
645
|
+
`review_list`, and `resolve_review`.
|
|
645
646
|
Writes use atomic file replacement and a shared lock; concurrent writes either
|
|
646
647
|
complete or report that the caller should retry. After a crashed writer, stop
|
|
647
648
|
the service, verify the PID in `runtime/brain-write.lock` is no longer running,
|
|
@@ -651,10 +652,60 @@ and remove that lock before restarting.
|
|
|
651
652
|
agent-brain remember --json '{"scope":"project","type":"observation","title":"Refresh handling","content":"Investigate concurrent refresh requests."}'
|
|
652
653
|
agent-brain search "refresh"
|
|
653
654
|
agent-brain get ITEM_ID
|
|
655
|
+
agent-brain amend ITEM_ID --revision REVISION --json '{"content":"Corrected text."}'
|
|
654
656
|
agent-brain deprecate ITEM_ID --revision REVISION "No longer applies"
|
|
655
657
|
agent-brain promote ITEM_ID --scope global --revision REVISION --confirmed
|
|
656
658
|
```
|
|
657
659
|
|
|
660
|
+
`amend_knowledge` revises an active item in place, keeping its id, file name, scope,
|
|
661
|
+
type, authority, and history, and updating only the fields you pass. Use it to correct
|
|
662
|
+
or extend knowledge instead of writing a near-duplicate. Scope, type, authority, and
|
|
663
|
+
review severity are not amendable; if one of those is wrong, deprecate the item and
|
|
664
|
+
write a corrected one.
|
|
665
|
+
|
|
666
|
+
#### File names
|
|
667
|
+
|
|
668
|
+
`remember` names each file after the item id. Pass `filename` to choose the name
|
|
669
|
+
instead, as lower-case words separated by single dashes and no extension:
|
|
670
|
+
|
|
671
|
+
```sh
|
|
672
|
+
agent-brain remember --json '{"scope":"project","type":"observation","title":"Build flow","content":"...","filename":"build-flow"}'
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
If that name is taken, the item id is appended rather than overwriting anything. The
|
|
676
|
+
name is storage only and is never written into the metadata: the `id` in the front
|
|
677
|
+
matter is the identity, so files stay safe to rename by hand and the item is still found
|
|
678
|
+
by id afterwards.
|
|
679
|
+
|
|
680
|
+
#### Review findings
|
|
681
|
+
|
|
682
|
+
Review knowledge is a numbered, prioritized list. `remember` requires a
|
|
683
|
+
`severity` of `critical`, `high`, `medium`, or `low` when the type is `review`,
|
|
684
|
+
and rejects `severity` for every other type. The store assigns the finding
|
|
685
|
+
label itself: `C1`, `H1`, `M1`, `L1`, counting up per severity within the scope.
|
|
686
|
+
Resolved numbers are never handed out again, so a label always refers to the
|
|
687
|
+
same finding.
|
|
688
|
+
|
|
689
|
+
Each finding also carries a `state` of `open`, `fixed`, or `wontfix`, separate
|
|
690
|
+
from the `active` and `deprecated` status that controls recall. New findings
|
|
691
|
+
start `open`.
|
|
692
|
+
|
|
693
|
+
```sh
|
|
694
|
+
agent-brain review list
|
|
695
|
+
agent-brain review list --severity high --state open
|
|
696
|
+
agent-brain review resolve ITEM_ID --revision REVISION --state fixed "Fixed by using a stable command name"
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
`review_list` returns findings in severity then number order, open ones only
|
|
700
|
+
unless states are given, each with the revision needed to resolve it.
|
|
701
|
+
`resolve_review` records the state and reason and takes the finding out of active
|
|
702
|
+
recall, keeping its text and history. Mark a finding `wontfix` only when the user
|
|
703
|
+
has said it is intentional.
|
|
704
|
+
|
|
705
|
+
Finding labels are indexed, so `search_knowledge` for `H2` finds that finding.
|
|
706
|
+
Review files written before these fields existed keep parsing; they simply carry
|
|
707
|
+
no label and stay out of the finding list.
|
|
708
|
+
|
|
658
709
|
Remembered deductions default to inferred authority and must remain observations,
|
|
659
710
|
memories, or review records. Global writes require explicit user authority.
|
|
660
711
|
Promotion copies an item, records its origin, preserves the original, and requires
|
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,12 @@ 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 resolve <id> --revision <hash> --state fixed|wontfix [reason]',
|
|
39
43
|
'agent-brain todo list | get <id> | add --json <task-object>',
|
|
40
44
|
'agent-brain todo update <id> --revision <hash> --json <changes>',
|
|
41
45
|
'agent-brain todo complete <id> --revision <hash>',
|
|
@@ -118,12 +122,31 @@ async function brainMain(argv, wrapperCommand) {
|
|
|
118
122
|
throw new Error('Promotion requires --confirmed after explicit user approval');
|
|
119
123
|
print(store.promote(requireId(), store_1.scopeSchema.parse(values.scope), requireRevision()));
|
|
120
124
|
break;
|
|
125
|
+
case 'amend':
|
|
126
|
+
print(store.amend(requireId(), requireRevision(), store_1.knowledgeChanges.parse(json())));
|
|
127
|
+
break;
|
|
121
128
|
case 'deprecate':
|
|
122
129
|
print(store.deprecate(requireId(), requireRevision(), positionals.slice(1).join(' ')));
|
|
123
130
|
break;
|
|
124
131
|
case 'skills':
|
|
125
132
|
print(positionals[0] ? (0, skills_1.getSkill)(store, positionals[0]) : (0, skills_1.listSkills)(store));
|
|
126
133
|
break;
|
|
134
|
+
case 'review': {
|
|
135
|
+
const operation = positionals.shift() ?? 'list';
|
|
136
|
+
if (operation === 'list') {
|
|
137
|
+
print(store.reviews({
|
|
138
|
+
...(values.severity ? { severity: [store_1.severitySchema.parse(values.severity)] } : {}),
|
|
139
|
+
...(values.state ? { state: [store_1.reviewStateSchema.parse(values.state)] } : {})
|
|
140
|
+
}).map((item) => ({ finding: item.finding, severity: item.severity, state: item.state ?? 'open', title: item.title, id: item.id, revision: item.revision })));
|
|
141
|
+
}
|
|
142
|
+
else if (operation === 'resolve') {
|
|
143
|
+
const state = zod_1.z.enum(['fixed', 'wontfix']).parse(values.state);
|
|
144
|
+
print(store.resolveReview(requireId(), requireRevision(), state, positionals.slice(1).join(' ') || undefined));
|
|
145
|
+
}
|
|
146
|
+
else
|
|
147
|
+
throw new Error('Usage: agent-brain review list | resolve <id> --revision <hash> --state fixed|wontfix');
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
127
150
|
case 'sync': {
|
|
128
151
|
const action = positionals[0];
|
|
129
152
|
if (!action) {
|
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,7 +22,7 @@ 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) => {
|
|
@@ -59,6 +59,9 @@ function createBrainServer(store) {
|
|
|
59
59
|
}, true, ({ query, ...options }) => store.search(query, options));
|
|
60
60
|
tool('get_knowledge', 'Read a complete knowledge item and its current revision.', { id }, true, ({ id }) => store.get(id));
|
|
61
61
|
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));
|
|
62
|
+
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.', {
|
|
63
|
+
id, revision, changes: store_1.knowledgeChanges
|
|
64
|
+
}, false, ({ id, revision, changes }) => store.amend(id, revision, changes));
|
|
62
65
|
tool('promote', 'Copy active knowledge to another available scope with user authority. Requires explicit user approval; the original is preserved.', {
|
|
63
66
|
id, scope: store_1.scopeSchema, revision, confirmed: zod_1.z.literal(true)
|
|
64
67
|
}, false, ({ id, scope, revision }) => store.promote(id, scope, revision));
|
|
@@ -68,6 +71,15 @@ function createBrainServer(store) {
|
|
|
68
71
|
tool('review_context', 'Retrieve constraints, decisions, prior review findings, and path-specific knowledge for a code review.', {
|
|
69
72
|
task: zod_1.z.string().max(10000), files: zod_1.z.array(zod_1.z.string()).max(100).optional()
|
|
70
73
|
}, true, ({ task, files }) => store.context(`review constraint decision ${task}`, files));
|
|
74
|
+
tool('review_list', 'List review findings in severity then number order. Open findings only unless states are given. Each finding carries its label, severity, state, and the revision needed to resolve it.', {
|
|
75
|
+
severity: zod_1.z.array(store_1.severitySchema).optional(), state: zod_1.z.array(store_1.reviewStateSchema).optional()
|
|
76
|
+
}, true, ({ severity, state }) => store.reviews({ severity, state }).map((item) => ({
|
|
77
|
+
finding: item.finding, severity: item.severity, state: item.state ?? 'open', title: item.title,
|
|
78
|
+
applies_to: item.applies_to, id: item.id, revision: item.revision
|
|
79
|
+
})));
|
|
80
|
+
tool('resolve_review', 'Close a review finding as fixed or wontfix, with a reason and the revision you last read. Use wontfix only when the user has said the finding is intentional.', {
|
|
81
|
+
id, revision, state: zod_1.z.enum(['fixed', 'wontfix']), reason: zod_1.z.string().max(2000).optional()
|
|
82
|
+
}, false, ({ id, revision, state, reason }) => store.resolveReview(id, revision, state, reason));
|
|
71
83
|
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
84
|
tool('todo_get', 'Read a task and its revision.', { id }, true, ({ id }) => (0, todos_1.getTodo)(store, id));
|
|
73
85
|
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/store.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BrainStore = exports.knowledgeDirectories = exports.knowledgeMetadata = exports.knowledgeInput = exports.typeSchema = exports.scopeSchema = void 0;
|
|
3
|
+
exports.BrainStore = exports.knowledgeDirectories = exports.knowledgeChanges = exports.knowledgeMetadata = exports.knowledgeInput = exports.reviewStateSchema = exports.severitySchema = exports.typeSchema = exports.scopeSchema = void 0;
|
|
4
4
|
exports.readMarkdown = readMarkdown;
|
|
5
5
|
const fs = require("node:fs");
|
|
6
6
|
const path = require("node:path");
|
|
@@ -13,6 +13,10 @@ const utils_1 = require("../utils");
|
|
|
13
13
|
const config_1 = require("./config");
|
|
14
14
|
exports.scopeSchema = zod_1.z.enum(['global', 'project', 'default']);
|
|
15
15
|
exports.typeSchema = zod_1.z.enum(['rule', 'preference', 'convention', 'constraint', 'decision', 'spec', 'review', 'memory', 'observation']);
|
|
16
|
+
exports.severitySchema = zod_1.z.enum(['critical', 'high', 'medium', 'low']);
|
|
17
|
+
exports.reviewStateSchema = zod_1.z.enum(['open', 'fixed', 'wontfix']);
|
|
18
|
+
const severityPrefix = { critical: 'C', high: 'H', medium: 'M', low: 'L' };
|
|
19
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
16
20
|
exports.knowledgeInput = zod_1.z.object({
|
|
17
21
|
scope: exports.scopeSchema,
|
|
18
22
|
type: exports.typeSchema,
|
|
@@ -20,17 +24,32 @@ exports.knowledgeInput = zod_1.z.object({
|
|
|
20
24
|
content: zod_1.z.string().trim().min(1).max(200000),
|
|
21
25
|
authority: zod_1.z.enum(['user', 'inferred']).default('inferred'),
|
|
22
26
|
recall: zod_1.z.enum(['always', 'relevant']).default('relevant'),
|
|
27
|
+
severity: exports.severitySchema.optional(),
|
|
23
28
|
tags: zod_1.z.array(zod_1.z.string().max(100)).max(50).default([]),
|
|
24
|
-
applies_to: zod_1.z.array(zod_1.z.string().max(500)).max(50).default([])
|
|
29
|
+
applies_to: zod_1.z.array(zod_1.z.string().max(500)).max(50).default([]),
|
|
30
|
+
// Naming only. The id in the front matter stays the identity, so files can be renamed freely.
|
|
31
|
+
filename: zod_1.z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Use lower-case words separated by single dashes, with no extension').max(100).optional()
|
|
25
32
|
}).strict();
|
|
26
|
-
|
|
33
|
+
// finding and state stay optional so review files written before they existed keep parsing.
|
|
34
|
+
exports.knowledgeMetadata = exports.knowledgeInput.omit({ content: true, filename: true }).extend({
|
|
27
35
|
id: zod_1.z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/),
|
|
28
36
|
status: zod_1.z.enum(['active', 'deprecated']).default('active'),
|
|
37
|
+
finding: zod_1.z.string().regex(/^[CHML][1-9][0-9]*$/).optional(),
|
|
38
|
+
state: exports.reviewStateSchema.optional(),
|
|
29
39
|
created: zod_1.z.string().default(''),
|
|
30
40
|
updated: zod_1.z.string().default(''),
|
|
31
41
|
reason: zod_1.z.string().optional(),
|
|
32
42
|
promoted_from: zod_1.z.string().optional()
|
|
33
43
|
}).strict();
|
|
44
|
+
// Spelled out rather than derived from knowledgeInput: .partial() keeps .default(), so a
|
|
45
|
+
// derived schema would silently reset every field the caller did not mention.
|
|
46
|
+
exports.knowledgeChanges = zod_1.z.object({
|
|
47
|
+
title: zod_1.z.string().trim().min(1).max(300).optional(),
|
|
48
|
+
content: zod_1.z.string().trim().min(1).max(200000).optional(),
|
|
49
|
+
recall: zod_1.z.enum(['always', 'relevant']).optional(),
|
|
50
|
+
tags: zod_1.z.array(zod_1.z.string().max(100)).max(50).optional(),
|
|
51
|
+
applies_to: zod_1.z.array(zod_1.z.string().max(500)).max(50).optional()
|
|
52
|
+
}).strict();
|
|
34
53
|
exports.knowledgeDirectories = {
|
|
35
54
|
rule: 'rules', preference: 'preferences', convention: 'conventions', constraint: 'constraints',
|
|
36
55
|
decision: 'decisions', spec: 'specs', review: 'reviews', memory: 'memory', observation: 'observations'
|
|
@@ -180,7 +199,7 @@ class BrainStore {
|
|
|
180
199
|
this.db.prepare('DELETE FROM knowledge WHERE environment = ?').run(environment);
|
|
181
200
|
const insert = this.db.prepare('INSERT INTO knowledge(source, environment, title, content, tags) VALUES (?, ?, ?, ?, ?)');
|
|
182
201
|
for (const item of items)
|
|
183
|
-
insert.run(item.source, environment, item.title, item.content, item.tags.join(' '));
|
|
202
|
+
insert.run(item.source, environment, item.title, item.content, [...item.tags, item.finding ?? ''].join(' ').trim());
|
|
184
203
|
this.db.exec('COMMIT');
|
|
185
204
|
}
|
|
186
205
|
catch (error) {
|
|
@@ -219,15 +238,74 @@ class BrainStore {
|
|
|
219
238
|
}
|
|
220
239
|
if (value.scope === 'global' && value.authority !== 'user')
|
|
221
240
|
throw new Error('Global writes require explicit user authority.');
|
|
241
|
+
if (value.type === 'review' && !value.severity)
|
|
242
|
+
throw new Error('Review findings require a severity: critical, high, medium, or low.');
|
|
243
|
+
if (value.type !== 'review' && value.severity)
|
|
244
|
+
throw new Error('Severity applies only to review findings.');
|
|
222
245
|
return this.writeLocked(() => {
|
|
223
246
|
const now = new Date().toISOString();
|
|
224
|
-
const { content, ...metadata } = value;
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
this.
|
|
247
|
+
const { content, filename, ...metadata } = value;
|
|
248
|
+
const review = value.severity ? { finding: this.nextFinding(value.scope, value.severity), state: 'open' } : {};
|
|
249
|
+
const item = { ...metadata, ...review, id: (0, node_crypto_1.randomUUID)(), status: 'active', created: now, updated: now };
|
|
250
|
+
const directory = path.join(this.scopeDirectory(value.scope), exports.knowledgeDirectories[value.type]);
|
|
251
|
+
this.atomicWrite(this.availableFile(directory, filename, item.id), `---\n${(0, yaml_1.stringify)(item)}---\n\n${content}\n`);
|
|
228
252
|
return this.allKnowledge().find((entry) => entry.id === item.id);
|
|
229
253
|
});
|
|
230
254
|
}
|
|
255
|
+
/** Chosen name when one is given and free, otherwise the same name with the id appended. */
|
|
256
|
+
availableFile(directory, filename, id) {
|
|
257
|
+
if (!filename)
|
|
258
|
+
return path.join(directory, `${id}.md`);
|
|
259
|
+
const preferred = path.join(directory, `${filename}.md`);
|
|
260
|
+
return fs.existsSync(preferred) ? path.join(directory, `${filename}-${id}.md`) : preferred;
|
|
261
|
+
}
|
|
262
|
+
/** Revise an item in place, keeping its id, filename, scope, type, authority, and history. */
|
|
263
|
+
amend(id, revision, changes) {
|
|
264
|
+
const parsed = exports.knowledgeChanges.parse(changes);
|
|
265
|
+
if (Object.keys(parsed).length === 0)
|
|
266
|
+
throw new Error('Supply at least one field to change.');
|
|
267
|
+
return this.writeLocked(() => {
|
|
268
|
+
const item = this.get(id);
|
|
269
|
+
if (item.revision !== revision)
|
|
270
|
+
throw new Error('Knowledge changed; read the current revision before amending.');
|
|
271
|
+
if (item.status !== 'active')
|
|
272
|
+
throw new Error('Deprecated knowledge cannot be amended.');
|
|
273
|
+
const { source, revision: _revision, content, ...metadata } = item;
|
|
274
|
+
const { content: nextContent, ...nextMetadata } = parsed;
|
|
275
|
+
this.atomicWrite(source, `---\n${(0, yaml_1.stringify)({ ...metadata, ...nextMetadata, updated: new Date().toISOString() })}---\n\n${nextContent ?? content}\n`);
|
|
276
|
+
return this.get(id);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/** Next unused finding label for a severity, counting resolved findings so numbers are never reused. */
|
|
280
|
+
nextFinding(scope, severity) {
|
|
281
|
+
const prefix = severityPrefix[severity];
|
|
282
|
+
const used = this.allKnowledge()
|
|
283
|
+
.filter((item) => item.scope === scope && item.type === 'review' && item.finding?.startsWith(prefix))
|
|
284
|
+
.map((item) => Number.parseInt(item.finding.slice(prefix.length), 10))
|
|
285
|
+
.filter((sequence) => Number.isSafeInteger(sequence));
|
|
286
|
+
return `${prefix}${Math.max(0, ...used) + 1}`;
|
|
287
|
+
}
|
|
288
|
+
/** Review findings in severity then sequence order. Open findings only unless states are given. */
|
|
289
|
+
reviews(options = {}) {
|
|
290
|
+
const sequence = (item) => Number.parseInt(item.finding.slice(1), 10);
|
|
291
|
+
return this.allKnowledge()
|
|
292
|
+
.filter((item) => item.type === 'review' && item.finding && item.severity)
|
|
293
|
+
.filter((item) => !options.severity || options.severity.includes(item.severity))
|
|
294
|
+
.filter((item) => (options.state ?? ['open']).includes(item.state ?? 'open'))
|
|
295
|
+
.sort((left, right) => severityOrder[left.severity] - severityOrder[right.severity] || sequence(left) - sequence(right));
|
|
296
|
+
}
|
|
297
|
+
resolveReview(id, revision, state, reason) {
|
|
298
|
+
return this.writeLocked(() => {
|
|
299
|
+
const item = this.get(id);
|
|
300
|
+
if (item.type !== 'review' || !item.finding)
|
|
301
|
+
throw new Error('Only review findings can be resolved.');
|
|
302
|
+
if (item.revision !== revision)
|
|
303
|
+
throw new Error('Knowledge changed; read the current revision before updating.');
|
|
304
|
+
const { source, revision: _revision, content, ...metadata } = item;
|
|
305
|
+
this.atomicWrite(source, `---\n${(0, yaml_1.stringify)({ ...metadata, state, status: 'deprecated', updated: new Date().toISOString(), ...(reason ? { reason } : {}) })}---\n\n${content}\n`);
|
|
306
|
+
return this.get(id);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
231
309
|
deprecate(id, revision, reason) {
|
|
232
310
|
return this.writeLocked(() => {
|
|
233
311
|
const item = this.get(id);
|
|
@@ -248,8 +326,10 @@ class BrainStore {
|
|
|
248
326
|
if (item.scope === scope)
|
|
249
327
|
throw new Error('Knowledge already belongs to this scope.');
|
|
250
328
|
const { source: _source, revision: _revision, content, ...metadata } = item;
|
|
251
|
-
const
|
|
252
|
-
|
|
329
|
+
const renumbered = item.severity ? { finding: this.nextFinding(scope, item.severity) } : {};
|
|
330
|
+
const promoted = { ...metadata, ...renumbered, id: (0, node_crypto_1.randomUUID)(), scope, authority: 'user', promoted_from: id, updated: new Date().toISOString() };
|
|
331
|
+
const target = path.join(this.scopeDirectory(scope), exports.knowledgeDirectories[item.type]);
|
|
332
|
+
this.atomicWrite(this.availableFile(target, path.basename(item.source, '.md'), promoted.id), `---\n${(0, yaml_1.stringify)(promoted)}---\n\n${content}\n`);
|
|
253
333
|
return this.get(promoted.id);
|
|
254
334
|
});
|
|
255
335
|
}
|
package/dist/brain/todos.js
CHANGED
|
@@ -59,7 +59,18 @@ function addTodo(store, input) {
|
|
|
59
59
|
return writeTodo(store, { ...value, id: (0, node_crypto_1.randomUUID)(), created: now, updated: now });
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
// Spelled out rather than todoInput.omit(...).partial(): .partial() keeps .default(), so
|
|
63
|
+
// updating one field would reset description, priority, labels, and notes to their defaults.
|
|
64
|
+
exports.todoChanges = zod_1.z.object({
|
|
65
|
+
title: zod_1.z.string().trim().min(1).max(300).optional(),
|
|
66
|
+
description: zod_1.z.string().trim().max(200000).optional(),
|
|
67
|
+
status: zod_1.z.enum(['todo', 'doing', 'blocked', 'done', 'cancelled']).optional(),
|
|
68
|
+
priority: zod_1.z.enum(['low', 'medium', 'high', 'critical']).optional(),
|
|
69
|
+
owner: zod_1.z.string().optional(),
|
|
70
|
+
due: zod_1.z.iso.date().optional(),
|
|
71
|
+
labels: zod_1.z.array(zod_1.z.string()).optional(),
|
|
72
|
+
notes: zod_1.z.string().optional()
|
|
73
|
+
}).strict();
|
|
63
74
|
function updateTodo(store, id, revision, changes) {
|
|
64
75
|
const parsed = exports.todoChanges.parse(changes);
|
|
65
76
|
return store.writeLocked(() => {
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseInvocation = parseInvocation;
|
|
4
|
-
const fs = require("fs");
|
|
5
4
|
const path = require("path");
|
|
6
5
|
const constants_1 = require("./constants");
|
|
6
|
+
const version_1 = require("./version");
|
|
7
7
|
const registry_1 = require("./agents/registry");
|
|
8
8
|
const types_1 = require("./agents/types");
|
|
9
9
|
const project_1 = require("./project");
|
|
@@ -299,7 +299,7 @@ function isVersionFlag(value) {
|
|
|
299
299
|
return value === '-V' || value === '--version';
|
|
300
300
|
}
|
|
301
301
|
function printVersion() {
|
|
302
|
-
process.stdout.write(`agent-run ${
|
|
302
|
+
process.stdout.write(`agent-run ${(0, version_1.packageVersion)()}\n`);
|
|
303
303
|
process.exit(0);
|
|
304
304
|
}
|
|
305
305
|
function printHelp(topic) {
|
|
@@ -347,7 +347,7 @@ function renderHelp(topic) {
|
|
|
347
347
|
return help[topic].join('\n');
|
|
348
348
|
}
|
|
349
349
|
return [
|
|
350
|
-
`agent-run ${
|
|
350
|
+
`agent-run ${(0, version_1.packageVersion)()}`,
|
|
351
351
|
'',
|
|
352
352
|
'Usage:',
|
|
353
353
|
' agent-run <codex|claude|gemini|grok|check|status|setup|generate|edit|update> [options]',
|
|
@@ -393,15 +393,6 @@ function renderHelp(topic) {
|
|
|
393
393
|
''
|
|
394
394
|
].join('\n');
|
|
395
395
|
}
|
|
396
|
-
function agentRunVersion() {
|
|
397
|
-
try {
|
|
398
|
-
const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf8'));
|
|
399
|
-
return typeof pkg.version === 'string' && pkg.version ? pkg.version : constants_1.PACKAGE_VERSION;
|
|
400
|
-
}
|
|
401
|
-
catch {
|
|
402
|
-
return constants_1.PACKAGE_VERSION;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
396
|
function normalizeCommandName(value) {
|
|
406
397
|
if (!value) {
|
|
407
398
|
return null;
|
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.REQUIRED_GLOBAL_TEMPLATES = exports.GENERATED_GITIGNORE_ENTRIES = exports.BRAIN_GITIGNORE_ENTRIES = exports.LOCAL_AI_DIRECTORY_NAMES = exports.LOCAL_AI_FILE_NAMES = exports.UNEXPANDED_TEMPLATE_RE = exports.
|
|
3
|
+
exports.REQUIRED_GLOBAL_TEMPLATES = exports.GENERATED_GITIGNORE_ENTRIES = exports.BRAIN_GITIGNORE_ENTRIES = exports.LOCAL_AI_DIRECTORY_NAMES = exports.LOCAL_AI_FILE_NAMES = exports.UNEXPANDED_TEMPLATE_RE = exports.VERBOSE_ENV = exports.CONFIG_DIR_ENV = exports.CONFIG_ROOT_OVERRIDE_ENV = exports.LIVE_DIR_NAME = exports.LOCAL_TEMPLATE_FILE_NAME = exports.MANIFEST_FILE_NAME = exports.CONFIG_ROOT_DEFAULTS_FILE_NAME = exports.IGNORE_FILE_NAME = exports.ENV_FILE_NAME = exports.IS_WINDOWS = void 0;
|
|
4
4
|
exports.IS_WINDOWS = process.platform === 'win32';
|
|
5
5
|
exports.ENV_FILE_NAME = '.agent-run.env';
|
|
6
6
|
exports.IGNORE_FILE_NAME = '.agent-run-ignore';
|
|
@@ -11,7 +11,6 @@ exports.LIVE_DIR_NAME = 'live';
|
|
|
11
11
|
exports.CONFIG_ROOT_OVERRIDE_ENV = 'AGENT_RUN_CONFIG_ROOT_OVERRIDE';
|
|
12
12
|
exports.CONFIG_DIR_ENV = 'AGENT_CONFIG_DIR';
|
|
13
13
|
exports.VERBOSE_ENV = 'AGENT_RUN_VERBOSE';
|
|
14
|
-
exports.PACKAGE_VERSION = '0.99.30';
|
|
15
14
|
exports.UNEXPANDED_TEMPLATE_RE = /\{\{[^}]+\}\}|\{%[^%]+%\}/;
|
|
16
15
|
exports.LOCAL_AI_FILE_NAMES = new Set([
|
|
17
16
|
'AGENTS.md',
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.packageVersion = packageVersion;
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
let cached = null;
|
|
7
|
+
/**
|
|
8
|
+
* The package version, read from package.json next to the compiled output.
|
|
9
|
+
* This is the single source; nothing else in the tree hardcodes a version.
|
|
10
|
+
*/
|
|
11
|
+
function packageVersion() {
|
|
12
|
+
if (cached === null) {
|
|
13
|
+
try {
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf8'));
|
|
15
|
+
cached = typeof pkg.version === 'string' && pkg.version ? pkg.version : '0.0.0';
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
cached = '0.0.0';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return cached;
|
|
22
|
+
}
|