@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 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`, and `review_context`.
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) {
@@ -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,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: 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) => {
@@ -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));
@@ -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
- exports.knowledgeMetadata = exports.knowledgeInput.omit({ content: true }).extend({
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 item = { ...metadata, id: (0, node_crypto_1.randomUUID)(), status: 'active', created: now, updated: now };
226
- const file = path.join(this.scopeDirectory(value.scope), exports.knowledgeDirectories[value.type], `${item.id}.md`);
227
- this.atomicWrite(file, `---\n${(0, yaml_1.stringify)(item)}---\n\n${content}\n`);
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 promoted = { ...metadata, id: (0, node_crypto_1.randomUUID)(), scope, authority: 'user', promoted_from: id, updated: new Date().toISOString() };
252
- this.atomicWrite(path.join(this.scopeDirectory(scope), exports.knowledgeDirectories[item.type], `${promoted.id}.md`), `---\n${(0, yaml_1.stringify)(promoted)}---\n\n${content}\n`);
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
  }
@@ -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
- exports.todoChanges = exports.todoInput.omit({ scope: true, source: true }).partial();
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 ${agentRunVersion()}\n`);
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 ${agentRunVersion()}`,
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.PACKAGE_VERSION = 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;
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',
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technomoron/agent-run",
3
- "version": "0.99.31",
3
+ "version": "0.99.32",
4
4
  "packageManager": "pnpm@10.34.5",
5
5
  "bin": {
6
6
  "agent-run": "dist/agent-run.js",