@forwardimpact/libwiki 0.2.10 → 0.2.12

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/src/boot.js CHANGED
@@ -1,17 +1,15 @@
1
- import { readFileSync, existsSync } from "node:fs";
2
1
  import path from "node:path";
2
+ import { yearMonth } from "@forwardimpact/libutil";
3
3
  import { parseClaims, filterExpired } from "./active-claims.js";
4
4
  import { MEMO_INBOX_MARKER, PRIORITY_INDEX_HEADING } from "./constants.js";
5
5
 
6
- function readIfExists(filePath) {
7
- if (!existsSync(filePath)) return null;
8
- return readFileSync(filePath, "utf-8");
6
+ function readIfExists(fs, filePath) {
7
+ if (!fs.existsSync(filePath)) return null;
8
+ return fs.readFileSync(filePath, "utf-8");
9
9
  }
10
10
 
11
- function currentStoryboardPath(wikiRoot, date) {
12
- const yyyy = date.getUTCFullYear();
13
- const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
14
- return path.join(wikiRoot, `storyboard-${yyyy}-M${mm}.md`);
11
+ function currentStoryboardPath(wikiRoot, today) {
12
+ return path.join(wikiRoot, `storyboard-${yearMonth(today)}.md`);
15
13
  }
16
14
 
17
15
  function extractSummary(text) {
@@ -146,20 +144,22 @@ function mapClaim(c) {
146
144
  };
147
145
  }
148
146
 
149
- /** Build the boot digest JSON object. */
150
- export function buildDigest({ wikiRoot, agent, today, _fs, _gh }) {
151
- const date = today instanceof Date ? today : new Date(today);
152
- const todayStr = date.toISOString().slice(0, 10);
153
-
147
+ /**
148
+ * Build the boot digest JSON object.
149
+ * @param {{wikiRoot: string, agent: string, today: string, fs: object}} options
150
+ * `fs` is the sync filesystem surface (`runtime.fsSync`); `today` is an ISO
151
+ * date string.
152
+ */
153
+ export function buildDigest({ wikiRoot, agent, today, fs }) {
154
154
  const summaryPath = path.join(wikiRoot, `${agent}.md`);
155
155
  const memoryPath = path.join(wikiRoot, "MEMORY.md");
156
- const storyboardPath = currentStoryboardPath(wikiRoot, date);
156
+ const storyboardPath = currentStoryboardPath(wikiRoot, today);
157
157
 
158
- const summaryText = readIfExists(summaryPath);
159
- const memoryText = readIfExists(memoryPath);
160
- const storyboardText = readIfExists(storyboardPath);
158
+ const summaryText = readIfExists(fs, summaryPath);
159
+ const memoryText = readIfExists(fs, memoryPath);
160
+ const storyboardText = readIfExists(fs, storyboardPath);
161
161
 
162
- const { active } = filterExpired(parseClaims(memoryText ?? ""), todayStr);
162
+ const { active } = filterExpired(parseClaims(memoryText ?? ""), today);
163
163
  const { owned, cross } = splitPriorities(
164
164
  parsePriorityTable(memoryText ?? ""),
165
165
  agent,
@@ -172,7 +172,7 @@ export function buildDigest({ wikiRoot, agent, today, _fs, _gh }) {
172
172
  claims: active.map(mapClaim),
173
173
  storyboard_items: parseStoryboardItems(storyboardText ?? "", agent),
174
174
  inbox_count: countInbox(summaryText),
175
- storyboard_path: existsSync(storyboardPath)
175
+ storyboard_path: fs.existsSync(storyboardPath)
176
176
  ? path.relative(path.dirname(wikiRoot) || ".", storyboardPath)
177
177
  : "",
178
178
  };
@@ -0,0 +1,284 @@
1
+ import { runMemoCommand } from "./commands/memo.js";
2
+ import { runRefreshCommand } from "./commands/refresh.js";
3
+ import { runInitCommand } from "./commands/init.js";
4
+ import { runPushCommand, runPullCommand } from "./commands/sync.js";
5
+ import { runBootCommand } from "./commands/boot.js";
6
+ import { runLogCommand } from "./commands/log.js";
7
+ import { runClaimCommand, runReleaseCommand } from "./commands/claim.js";
8
+ import { runInboxCommand } from "./commands/inbox.js";
9
+ import { runRotateCommand } from "./commands/rotate.js";
10
+ import { runAuditCommand } from "./commands/audit.js";
11
+ import { runFixCommand } from "./commands/fix.js";
12
+
13
+ /**
14
+ * Build the `fit-wiki` libcli definition. The agent/sender defaults read the
15
+ * injected `env` rather than the ambient `process.env` so this module carries
16
+ * no ambient dependency; the bin shim passes `runtime.proc.env` and the
17
+ * package version. Each subcommand carries a `handler` and (for subcommand-
18
+ * bearing commands) `args`/`argsUsage` so `cli.dispatch` can route to the
19
+ * per-command handler with a frozen `ctx`.
20
+ *
21
+ * @param {Record<string, string>} env - The process env (`runtime.proc.env`).
22
+ * @param {string} version - The package version string.
23
+ * @returns {object} The libcli definition.
24
+ */
25
+ export function createDefinition(env, version) {
26
+ const wikiRootOpt = {
27
+ "wiki-root": {
28
+ type: "string",
29
+ description: "Override wiki root directory (default: wiki)",
30
+ },
31
+ };
32
+
33
+ const agentOpt = {
34
+ agent: {
35
+ type: "string",
36
+ description:
37
+ "Agent name (falls back to LIBEVAL_AGENT_PROFILE, then staff-engineer)",
38
+ default: env.LIBEVAL_AGENT_PROFILE || "staff-engineer",
39
+ },
40
+ };
41
+
42
+ const todayOpt = {
43
+ today: {
44
+ type: "string",
45
+ description: "Override today's ISO date (testing)",
46
+ },
47
+ };
48
+
49
+ return {
50
+ name: "fit-wiki",
51
+ version,
52
+ description: "Wiki lifecycle management for the Kata agent system",
53
+ commands: [
54
+ {
55
+ name: "boot",
56
+ description:
57
+ "Print on-boot digest (priorities, claims, storyboard items) as JSON",
58
+ handler: runBootCommand,
59
+ options: {
60
+ ...agentOpt,
61
+ ...wikiRootOpt,
62
+ ...todayOpt,
63
+ format: {
64
+ type: "string",
65
+ description: "Output format: json (default) or markdown",
66
+ },
67
+ },
68
+ },
69
+ {
70
+ name: "log",
71
+ description:
72
+ "Append a decision/note/done entry to the current weekly log",
73
+ args: ["subcommand"],
74
+ argsUsage: "[subcommand]",
75
+ handler: runLogCommand,
76
+ options: {
77
+ ...agentOpt,
78
+ ...wikiRootOpt,
79
+ ...todayOpt,
80
+ surveyed: {
81
+ type: "string",
82
+ description: "Decision: routing levels surveyed",
83
+ },
84
+ chosen: { type: "string", description: "Decision: chosen action" },
85
+ rationale: { type: "string", description: "Decision: rationale" },
86
+ alternatives: {
87
+ type: "string",
88
+ description: "Decision: alternatives",
89
+ },
90
+ field: { type: "string", description: "Note: field heading" },
91
+ body: { type: "string", description: "Note: field body" },
92
+ },
93
+ },
94
+ {
95
+ name: "claim",
96
+ description:
97
+ "Claim a target in MEMORY.md ## Active Claims (refuses duplicates)",
98
+ handler: runClaimCommand,
99
+ options: {
100
+ ...agentOpt,
101
+ ...wikiRootOpt,
102
+ ...todayOpt,
103
+ target: {
104
+ type: "string",
105
+ description: "What is being claimed (spec id, PR id, etc.)",
106
+ },
107
+ branch: { type: "string", description: "Branch carrying the work" },
108
+ pr: { type: "string", description: "Optional PR id" },
109
+ "expires-at": {
110
+ type: "string",
111
+ description: "Override expiry ISO date (default claim+7d)",
112
+ },
113
+ },
114
+ },
115
+ {
116
+ name: "release",
117
+ description: "Release a claim (or all expired claims with --expired)",
118
+ handler: runReleaseCommand,
119
+ options: {
120
+ ...agentOpt,
121
+ ...wikiRootOpt,
122
+ ...todayOpt,
123
+ target: { type: "string", description: "Target to release" },
124
+ expired: {
125
+ type: "boolean",
126
+ description: "Release every row past expires_at",
127
+ },
128
+ },
129
+ },
130
+ {
131
+ name: "inbox",
132
+ description: "Triage the agent's Message Inbox (list/ack/promote/drop)",
133
+ args: ["subcommand"],
134
+ argsUsage: "[subcommand]",
135
+ handler: runInboxCommand,
136
+ options: {
137
+ ...agentOpt,
138
+ ...wikiRootOpt,
139
+ ...todayOpt,
140
+ index: {
141
+ type: "string",
142
+ description: "Bullet index (0-based) for ack/promote/drop",
143
+ },
144
+ owner: {
145
+ type: "string",
146
+ description: "Owner field when promoting (default: --agent)",
147
+ },
148
+ },
149
+ },
150
+ {
151
+ name: "rotate",
152
+ description: "Force-rotate the current weekly log to a sealed part",
153
+ handler: runRotateCommand,
154
+ options: {
155
+ ...agentOpt,
156
+ ...wikiRootOpt,
157
+ ...todayOpt,
158
+ },
159
+ },
160
+ {
161
+ name: "audit",
162
+ description:
163
+ "Audit the wiki against the declarative rule catalogue (line and word budgets, headings, decision blocks, storyboards, claims)",
164
+ handler: runAuditCommand,
165
+ options: {
166
+ ...wikiRootOpt,
167
+ ...todayOpt,
168
+ format: {
169
+ type: "string",
170
+ description: "Output format: text (default) or json",
171
+ },
172
+ },
173
+ },
174
+ {
175
+ name: "fix",
176
+ description:
177
+ "Auto-fix wiki audit findings using an AI agent (technical-writer, Haiku)",
178
+ handler: runFixCommand,
179
+ options: {
180
+ ...wikiRootOpt,
181
+ ...todayOpt,
182
+ },
183
+ },
184
+ {
185
+ name: "memo",
186
+ description: "Send a cross-team memo into a teammate's Message Inbox",
187
+ handler: runMemoCommand,
188
+ options: {
189
+ from: {
190
+ type: "string",
191
+ description:
192
+ "Sender agent name (falls back to LIBEVAL_AGENT_PROFILE env var)",
193
+ default: env.LIBEVAL_AGENT_PROFILE,
194
+ },
195
+ to: {
196
+ type: "string",
197
+ description:
198
+ 'Target agent name, or "all" to broadcast (sender is skipped)',
199
+ },
200
+ message: {
201
+ type: "string",
202
+ description: "Memo text",
203
+ },
204
+ ...wikiRootOpt,
205
+ },
206
+ },
207
+ {
208
+ name: "refresh",
209
+ description:
210
+ "Regenerate XmR and obstacle/experiment marker blocks in a storyboard",
211
+ args: ["storyboard-path"],
212
+ argsUsage: "[storyboard-path]",
213
+ handler: runRefreshCommand,
214
+ options: {
215
+ format: {
216
+ type: "string",
217
+ description: "Output format: (default off) or json",
218
+ },
219
+ },
220
+ },
221
+ {
222
+ name: "init",
223
+ description: "Bootstrap a wiki working tree and scaffold Active Claims",
224
+ handler: runInitCommand,
225
+ options: {
226
+ ...wikiRootOpt,
227
+ "skills-dir": {
228
+ type: "string",
229
+ description: "Override skills directory (default: .claude/skills)",
230
+ },
231
+ },
232
+ },
233
+ {
234
+ name: "push",
235
+ description: "Commit and push local wiki changes to the remote",
236
+ handler: runPushCommand,
237
+ options: { ...wikiRootOpt },
238
+ },
239
+ {
240
+ name: "pull",
241
+ description: "Pull remote wiki changes into the local working tree",
242
+ handler: runPullCommand,
243
+ options: { ...wikiRootOpt },
244
+ },
245
+ ],
246
+ globalOptions: {
247
+ help: { type: "boolean", short: "h", description: "Show this help" },
248
+ version: { type: "boolean", description: "Show version" },
249
+ json: {
250
+ type: "boolean",
251
+ description: "Render --help output as JSON",
252
+ },
253
+ },
254
+ examples: [
255
+ "fit-wiki boot --agent staff-engineer",
256
+ 'fit-wiki log decision --agent staff-engineer --surveyed "..." --chosen "..." --rationale "..."',
257
+ "fit-wiki claim --agent staff-engineer --target spec-NNNN --branch claude/...",
258
+ "fit-wiki release --agent staff-engineer --target spec-NNNN",
259
+ "fit-wiki inbox list --agent staff-engineer",
260
+ "fit-wiki rotate --agent staff-engineer",
261
+ "fit-wiki audit",
262
+ "fit-wiki fix",
263
+ 'fit-wiki memo --from staff-engineer --to security-engineer --message "audit d642ff0c"',
264
+ "fit-wiki refresh",
265
+ "fit-wiki init",
266
+ "fit-wiki push",
267
+ "fit-wiki pull",
268
+ ],
269
+ documentation: [
270
+ {
271
+ title: "Operate a Predictable Agent Team",
272
+ url: "https://www.forwardimpact.team/docs/libraries/predictable-team/index.md",
273
+ description:
274
+ "End-to-end guide to wiki memory, XmR charts, and team coordination.",
275
+ },
276
+ {
277
+ title: "Send a Memo or Update a Storyboard",
278
+ url: "https://www.forwardimpact.team/docs/libraries/predictable-team/wiki-operations/index.md",
279
+ description:
280
+ "Send cross-team memos, refresh storyboard charts, and sync the wiki.",
281
+ },
282
+ ],
283
+ };
284
+ }
@@ -1,26 +1,27 @@
1
- import fsAsync from "node:fs/promises";
2
1
  import path from "node:path";
3
2
  import {
4
- Finder,
5
3
  emitFindingsJson,
6
4
  emitFindingsText,
7
5
  runRules,
8
6
  } from "@forwardimpact/libutil";
9
7
  import { RULES } from "../audit/rules.js";
10
8
  import { buildContext, resolveScope } from "../audit/scopes.js";
9
+ import { currentDayIso } from "../util/clock.js";
10
+ import { resolveProjectRoot } from "../util/wiki-dir.js";
11
11
 
12
12
  /** Run the wiki audit and emit findings. JSON via --format json. */
13
- export function runAuditCommand(values, _args, _cli) {
14
- const finder = new Finder(fsAsync, { debug() {} }, process);
15
- const projectRoot = finder.findProjectRoot(process.cwd());
16
- const wikiRoot = values["wiki-root"] || path.join(projectRoot, "wiki");
17
- const today = values.today || new Date().toISOString().slice(0, 10);
13
+ export function runAuditCommand(ctx) {
14
+ const { runtime } = ctx.deps;
15
+ const options = ctx.options;
16
+ const projectRoot = resolveProjectRoot(runtime);
17
+ const wikiRoot = options["wiki-root"] || path.join(projectRoot, "wiki");
18
+ const today = options.today || currentDayIso(runtime);
18
19
 
19
- const ctx = buildContext({ wikiRoot, today });
20
- const findings = runRules(RULES, ctx, { resolveScope });
20
+ const auditCtx = buildContext({ wikiRoot, today, fs: runtime.fsSync });
21
+ const findings = runRules(RULES, auditCtx, { resolveScope });
21
22
 
22
- process.stdout.write(
23
- values.format === "json"
23
+ runtime.proc.stdout.write(
24
+ options.format === "json"
24
25
  ? emitFindingsJson(findings)
25
26
  : emitFindingsText(findings, {
26
27
  cwd: projectRoot,
@@ -28,5 +29,6 @@ export function runAuditCommand(values, _args, _cli) {
28
29
  }),
29
30
  );
30
31
 
31
- if (findings.some((f) => f.level === "fail")) process.exit(1);
32
+ if (findings.some((f) => f.level === "fail")) return { ok: false, code: 1 };
33
+ return { ok: true };
32
34
  }
@@ -1,7 +1,6 @@
1
- import fsAsync from "node:fs/promises";
2
- import path from "node:path";
3
- import { Finder } from "@forwardimpact/libutil";
4
1
  import { buildDigest } from "../boot.js";
2
+ import { currentDayIso } from "../util/clock.js";
3
+ import { resolveWikiRoot } from "../util/wiki-dir.js";
5
4
 
6
5
  function renderMarkdown(digest) {
7
6
  const lines = [];
@@ -41,21 +40,21 @@ function renderMarkdown(digest) {
41
40
  }
42
41
 
43
42
  /** Print the on-boot digest for the calling agent. JSON by default; --format markdown renders prose. */
44
- export function runBootCommand(values, _args, cli) {
43
+ export function runBootCommand(ctx) {
44
+ const { runtime } = ctx.deps;
45
+ const options = ctx.options;
45
46
  const agent =
46
- values.agent || process.env.LIBEVAL_AGENT_PROFILE || "staff-engineer";
47
+ options.agent || runtime.proc.env.LIBEVAL_AGENT_PROFILE || "staff-engineer";
47
48
 
48
- const logger = { debug() {} };
49
- const finder = new Finder(fsAsync, logger, process);
50
- const projectRoot = finder.findProjectRoot(process.cwd());
51
- const wikiRoot = values["wiki-root"] || path.join(projectRoot, "wiki");
52
- const today = values.today || new Date().toISOString().slice(0, 10);
49
+ const wikiRoot = resolveWikiRoot(runtime, options);
50
+ const today = options.today || currentDayIso(runtime);
53
51
 
54
- const digest = buildDigest({ wikiRoot, agent, today });
52
+ const digest = buildDigest({ wikiRoot, agent, today, fs: runtime.fsSync });
55
53
 
56
- if ((values.format || "json") === "markdown") {
57
- process.stdout.write(renderMarkdown(digest) + "\n");
54
+ if ((options.format || "json") === "markdown") {
55
+ runtime.proc.stdout.write(renderMarkdown(digest) + "\n");
58
56
  } else {
59
- process.stdout.write(JSON.stringify(digest, null, 2) + "\n");
57
+ runtime.proc.stdout.write(JSON.stringify(digest, null, 2) + "\n");
60
58
  }
59
+ return { ok: true };
61
60
  }
@@ -1,76 +1,87 @@
1
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
2
- import fsAsync from "node:fs/promises";
3
1
  import path from "node:path";
4
- import { Finder } from "@forwardimpact/libutil";
2
+ import { addDays } from "@forwardimpact/libutil";
5
3
  import {
6
4
  appendClaim,
7
5
  removeClaim,
8
6
  parseClaims,
9
7
  filterExpired,
10
8
  } from "../active-claims.js";
11
- import { createDefaultIo } from "../io.js";
9
+ import { currentDayIso } from "../util/clock.js";
10
+ import { resolveWikiRoot } from "../util/wiki-dir.js";
12
11
 
13
- function projectRoot(io) {
14
- const logger = { debug() {} };
15
- const finder = new Finder(fsAsync, logger, { cwd: io.cwd });
16
- return finder.findProjectRoot(io.cwd());
12
+ function readMemory(runtime, memPath) {
13
+ if (!runtime.fsSync.existsSync(memPath)) return "";
14
+ return runtime.fsSync.readFileSync(memPath, "utf-8");
17
15
  }
18
16
 
19
- function memoryPath(values, io) {
20
- const root = projectRoot(io);
21
- const wikiRoot = values["wiki-root"] || path.join(root, "wiki");
22
- return path.join(wikiRoot, "MEMORY.md");
17
+ function memoryPath(runtime, options) {
18
+ return path.join(resolveWikiRoot(runtime, options), "MEMORY.md");
23
19
  }
24
20
 
25
- function readMemory(memPath) {
26
- if (!existsSync(memPath)) return "";
27
- return readFileSync(memPath, "utf-8");
28
- }
29
-
30
- function addDays(today, n) {
31
- const d = new Date(today);
32
- d.setUTCDate(d.getUTCDate() + n);
33
- return d.toISOString().slice(0, 10);
21
+ async function pushWiki(wikiSync, runtime, message) {
22
+ if (!wikiSync) return;
23
+ try {
24
+ await wikiSync.inheritIdentity();
25
+ const result = await wikiSync.commitAndPush(message);
26
+ if (result.pushed)
27
+ runtime.proc.stdout.write("push: committed and pushed\n");
28
+ } catch (err) {
29
+ runtime.proc.stderr.write(`push failed (saved locally): ${err.message}\n`);
30
+ }
34
31
  }
35
32
 
36
33
  /** Insert a row into MEMORY.md `## Active Claims`. Refuses if (agent, target) already present. */
37
- export function runClaimCommand(values, _args, cli, io = createDefaultIo()) {
38
- const agent = values.agent || io.env.LIBEVAL_AGENT_PROFILE;
34
+ export async function runClaimCommand(ctx) {
35
+ const { runtime, wikiSync } = ctx.deps;
36
+ const options = ctx.options;
37
+ const agent = options.agent || runtime.proc.env.LIBEVAL_AGENT_PROFILE;
39
38
  if (!agent) {
40
- cli.usageError("claim requires --agent or LIBEVAL_AGENT_PROFILE");
41
- return io.exit(2);
39
+ return {
40
+ ok: false,
41
+ code: 2,
42
+ error: "claim requires --agent or LIBEVAL_AGENT_PROFILE",
43
+ };
42
44
  }
43
- if (!values.target || !values.branch) {
44
- cli.usageError("claim requires --target and --branch");
45
- return io.exit(2);
45
+ if (!options.target || !options.branch) {
46
+ return {
47
+ ok: false,
48
+ code: 2,
49
+ error: "claim requires --target and --branch",
50
+ };
46
51
  }
47
- const today = values.today || io.today();
48
- const expires = values["expires-at"] || addDays(today, 7);
49
- const memPath = memoryPath(values, io);
50
- const text = readMemory(memPath);
52
+ const today = options.today || currentDayIso(runtime);
53
+ const expires = options["expires-at"] || addDays(today, 7);
54
+ const memPath = memoryPath(runtime, options);
55
+ const text = readMemory(runtime, memPath);
51
56
  const result = appendClaim(text, {
52
57
  agent,
53
- target: values.target,
54
- branch: values.branch,
55
- pr: values.pr || null,
58
+ target: options.target,
59
+ branch: options.branch,
60
+ pr: options.pr || null,
56
61
  claimed_at: today,
57
62
  expires_at: expires,
58
63
  });
59
64
  if (!result.inserted) {
60
- io.stderr(`claim already exists for ${agent}/${values.target}\n`);
61
- return io.exit(2);
65
+ runtime.proc.stderr.write(
66
+ `claim already exists for ${agent}/${options.target}\n`,
67
+ );
68
+ return { ok: false, code: 2 };
62
69
  }
63
- writeFileSync(memPath, result.text);
64
- io.stdout(`claimed ${values.target} (expires ${expires})\n`);
70
+ runtime.fsSync.writeFileSync(memPath, result.text);
71
+ runtime.proc.stdout.write(`claimed ${options.target} (expires ${expires})\n`);
72
+ await pushWiki(wikiSync, runtime, `wiki: claim ${options.target}`);
73
+ return { ok: true };
65
74
  }
66
75
 
67
76
  /** Remove a claim row. `--expired` cleans every row past expires_at. */
68
- export function runReleaseCommand(values, _args, cli, io = createDefaultIo()) {
69
- const memPath = memoryPath(values, io);
70
- const text = readMemory(memPath);
77
+ export async function runReleaseCommand(ctx) {
78
+ const { runtime, wikiSync } = ctx.deps;
79
+ const options = ctx.options;
80
+ const memPath = memoryPath(runtime, options);
81
+ const text = readMemory(runtime, memPath);
71
82
 
72
- if (values.expired) {
73
- const today = values.today || io.today();
83
+ if (options.expired) {
84
+ const today = options.today || currentDayIso(runtime);
74
85
  const claims = parseClaims(text);
75
86
  const { expired } = filterExpired(claims, today);
76
87
  let current = text;
@@ -82,25 +93,36 @@ export function runReleaseCommand(values, _args, cli, io = createDefaultIo()) {
82
93
  count++;
83
94
  }
84
95
  }
85
- writeFileSync(memPath, current);
86
- io.stdout(`released ${count} expired claim(s)\n`);
87
- return;
96
+ runtime.fsSync.writeFileSync(memPath, current);
97
+ runtime.proc.stdout.write(`released ${count} expired claim(s)\n`);
98
+ await pushWiki(wikiSync, runtime, "wiki: release expired claims");
99
+ return { ok: true };
88
100
  }
89
101
 
90
- const agent = values.agent || io.env.LIBEVAL_AGENT_PROFILE;
102
+ const agent = options.agent || runtime.proc.env.LIBEVAL_AGENT_PROFILE;
91
103
  if (!agent) {
92
- cli.usageError("release requires --agent or --expired");
93
- return io.exit(2);
104
+ return {
105
+ ok: false,
106
+ code: 2,
107
+ error: "release requires --agent or --expired",
108
+ };
94
109
  }
95
- if (!values.target) {
96
- cli.usageError("release requires --target (or --expired)");
97
- return io.exit(2);
110
+ if (!options.target) {
111
+ return {
112
+ ok: false,
113
+ code: 2,
114
+ error: "release requires --target (or --expired)",
115
+ };
98
116
  }
99
- const result = removeClaim(text, { agent, target: values.target });
100
- writeFileSync(memPath, result.text);
117
+ const result = removeClaim(text, { agent, target: options.target });
118
+ runtime.fsSync.writeFileSync(memPath, result.text);
101
119
  if (!result.removed) {
102
- io.stdout(`no matching claim for ${agent}/${values.target}\n`);
120
+ runtime.proc.stdout.write(
121
+ `no matching claim for ${agent}/${options.target}\n`,
122
+ );
103
123
  } else {
104
- io.stdout(`released ${values.target}\n`);
124
+ runtime.proc.stdout.write(`released ${options.target}\n`);
125
+ await pushWiki(wikiSync, runtime, `wiki: release ${options.target}`);
105
126
  }
127
+ return { ok: true };
106
128
  }