@forwardimpact/libwiki 0.2.11 → 0.2.13
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/bin/fit-wiki.js +49 -286
- package/package.json +1 -1
- package/src/agent-roster.js +7 -6
- package/src/audit/rules.js +5 -0
- package/src/audit/scopes.js +81 -28
- package/src/audit/status-row.js +51 -0
- package/src/block-renderer.js +7 -8
- package/src/boot.js +19 -19
- package/src/cli-definition.js +284 -0
- package/src/commands/audit.js +14 -12
- package/src/commands/boot.js +13 -14
- package/src/commands/claim.js +76 -82
- package/src/commands/fix.js +117 -37
- package/src/commands/inbox.js +61 -54
- package/src/commands/init.js +47 -53
- package/src/commands/log.js +58 -52
- package/src/commands/memo.js +59 -50
- package/src/commands/refresh.js +40 -36
- package/src/commands/rotate.js +26 -15
- package/src/commands/sync.js +17 -16
- package/src/index.js +1 -1
- package/src/issue-list-renderer.js +29 -28
- package/src/marker-migrator.js +8 -7
- package/src/marker-scanner.js +13 -8
- package/src/memo-writer.js +7 -6
- package/src/skill-roster.js +7 -3
- package/src/status.js +19 -0
- package/src/util/clock.js +13 -0
- package/src/util/wiki-dir.js +24 -0
- package/src/weekly-log.js +37 -37
- package/src/wiki-sync.js +164 -0
- package/src/build-repo.js +0 -20
- package/src/io.js +0 -77
- package/src/wiki-repo.js +0 -167
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,
|
|
12
|
-
|
|
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
|
-
/**
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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,
|
|
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 ?? ""),
|
|
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
|
+
}
|
package/src/commands/audit.js
CHANGED
|
@@ -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(
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
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
|
|
20
|
-
const findings = runRules(RULES,
|
|
20
|
+
const auditCtx = buildContext({ wikiRoot, today, fs: runtime.fsSync });
|
|
21
|
+
const findings = runRules(RULES, auditCtx, { resolveScope });
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
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"))
|
|
32
|
+
if (findings.some((f) => f.level === "fail")) return { ok: false, code: 1 };
|
|
33
|
+
return { ok: true };
|
|
32
34
|
}
|
package/src/commands/boot.js
CHANGED
|
@@ -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(
|
|
43
|
+
export function runBootCommand(ctx) {
|
|
44
|
+
const { runtime } = ctx.deps;
|
|
45
|
+
const options = ctx.options;
|
|
45
46
|
const agent =
|
|
46
|
-
|
|
47
|
+
options.agent || runtime.proc.env.LIBEVAL_AGENT_PROFILE || "staff-engineer";
|
|
47
48
|
|
|
48
|
-
const
|
|
49
|
-
const
|
|
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 ((
|
|
57
|
-
|
|
54
|
+
if ((options.format || "json") === "markdown") {
|
|
55
|
+
runtime.proc.stdout.write(renderMarkdown(digest) + "\n");
|
|
58
56
|
} else {
|
|
59
|
-
|
|
57
|
+
runtime.proc.stdout.write(JSON.stringify(digest, null, 2) + "\n");
|
|
60
58
|
}
|
|
59
|
+
return { ok: true };
|
|
61
60
|
}
|
package/src/commands/claim.js
CHANGED
|
@@ -1,102 +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 {
|
|
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 {
|
|
12
|
-
import {
|
|
9
|
+
import { currentDayIso } from "../util/clock.js";
|
|
10
|
+
import { resolveWikiRoot } from "../util/wiki-dir.js";
|
|
13
11
|
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
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");
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
function memoryPath(
|
|
21
|
-
|
|
22
|
-
const wikiRoot = values["wiki-root"] || path.join(root, "wiki");
|
|
23
|
-
return path.join(wikiRoot, "MEMORY.md");
|
|
17
|
+
function memoryPath(runtime, options) {
|
|
18
|
+
return path.join(resolveWikiRoot(runtime, options), "MEMORY.md");
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
function
|
|
27
|
-
if (!
|
|
28
|
-
return readFileSync(memPath, "utf-8");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function addDays(today, n) {
|
|
32
|
-
const d = new Date(today);
|
|
33
|
-
d.setUTCDate(d.getUTCDate() + n);
|
|
34
|
-
return d.toISOString().slice(0, 10);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function pushWiki(repoFactory, values, io, message) {
|
|
38
|
-
if (!repoFactory) return;
|
|
21
|
+
async function pushWiki(wikiSync, runtime, message) {
|
|
22
|
+
if (!wikiSync) return;
|
|
39
23
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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");
|
|
44
28
|
} catch (err) {
|
|
45
|
-
|
|
29
|
+
runtime.proc.stderr.write(`push failed (saved locally): ${err.message}\n`);
|
|
46
30
|
}
|
|
47
31
|
}
|
|
48
32
|
|
|
49
33
|
/** Insert a row into MEMORY.md `## Active Claims`. Refuses if (agent, target) already present. */
|
|
50
|
-
export async function runClaimCommand(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
io = createDefaultIo(),
|
|
55
|
-
repoFactory = buildRepo,
|
|
56
|
-
) {
|
|
57
|
-
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;
|
|
58
38
|
if (!agent) {
|
|
59
|
-
|
|
60
|
-
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
code: 2,
|
|
42
|
+
error: "claim requires --agent or LIBEVAL_AGENT_PROFILE",
|
|
43
|
+
};
|
|
61
44
|
}
|
|
62
|
-
if (!
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
if (!options.target || !options.branch) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
code: 2,
|
|
49
|
+
error: "claim requires --target and --branch",
|
|
50
|
+
};
|
|
65
51
|
}
|
|
66
|
-
const today =
|
|
67
|
-
const expires =
|
|
68
|
-
const memPath = memoryPath(
|
|
69
|
-
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);
|
|
70
56
|
const result = appendClaim(text, {
|
|
71
57
|
agent,
|
|
72
|
-
target:
|
|
73
|
-
branch:
|
|
74
|
-
pr:
|
|
58
|
+
target: options.target,
|
|
59
|
+
branch: options.branch,
|
|
60
|
+
pr: options.pr || null,
|
|
75
61
|
claimed_at: today,
|
|
76
62
|
expires_at: expires,
|
|
77
63
|
});
|
|
78
64
|
if (!result.inserted) {
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
runtime.proc.stderr.write(
|
|
66
|
+
`claim already exists for ${agent}/${options.target}\n`,
|
|
67
|
+
);
|
|
68
|
+
return { ok: false, code: 2 };
|
|
81
69
|
}
|
|
82
|
-
writeFileSync(memPath, result.text);
|
|
83
|
-
|
|
84
|
-
await pushWiki(
|
|
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 };
|
|
85
74
|
}
|
|
86
75
|
|
|
87
76
|
/** Remove a claim row. `--expired` cleans every row past expires_at. */
|
|
88
|
-
export async function runReleaseCommand(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
repoFactory = buildRepo,
|
|
94
|
-
) {
|
|
95
|
-
const memPath = memoryPath(values, io);
|
|
96
|
-
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);
|
|
97
82
|
|
|
98
|
-
if (
|
|
99
|
-
const today =
|
|
83
|
+
if (options.expired) {
|
|
84
|
+
const today = options.today || currentDayIso(runtime);
|
|
100
85
|
const claims = parseClaims(text);
|
|
101
86
|
const { expired } = filterExpired(claims, today);
|
|
102
87
|
let current = text;
|
|
@@ -108,27 +93,36 @@ export async function runReleaseCommand(
|
|
|
108
93
|
count++;
|
|
109
94
|
}
|
|
110
95
|
}
|
|
111
|
-
writeFileSync(memPath, current);
|
|
112
|
-
|
|
113
|
-
await pushWiki(
|
|
114
|
-
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 };
|
|
115
100
|
}
|
|
116
101
|
|
|
117
|
-
const agent =
|
|
102
|
+
const agent = options.agent || runtime.proc.env.LIBEVAL_AGENT_PROFILE;
|
|
118
103
|
if (!agent) {
|
|
119
|
-
|
|
120
|
-
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
code: 2,
|
|
107
|
+
error: "release requires --agent or --expired",
|
|
108
|
+
};
|
|
121
109
|
}
|
|
122
|
-
if (!
|
|
123
|
-
|
|
124
|
-
|
|
110
|
+
if (!options.target) {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
code: 2,
|
|
114
|
+
error: "release requires --target (or --expired)",
|
|
115
|
+
};
|
|
125
116
|
}
|
|
126
|
-
const result = removeClaim(text, { agent, target:
|
|
127
|
-
writeFileSync(memPath, result.text);
|
|
117
|
+
const result = removeClaim(text, { agent, target: options.target });
|
|
118
|
+
runtime.fsSync.writeFileSync(memPath, result.text);
|
|
128
119
|
if (!result.removed) {
|
|
129
|
-
|
|
120
|
+
runtime.proc.stdout.write(
|
|
121
|
+
`no matching claim for ${agent}/${options.target}\n`,
|
|
122
|
+
);
|
|
130
123
|
} else {
|
|
131
|
-
|
|
132
|
-
await pushWiki(
|
|
124
|
+
runtime.proc.stdout.write(`released ${options.target}\n`);
|
|
125
|
+
await pushWiki(wikiSync, runtime, `wiki: release ${options.target}`);
|
|
133
126
|
}
|
|
127
|
+
return { ok: true };
|
|
134
128
|
}
|