@forwardimpact/libwiki 0.2.35 → 0.3.1

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.
Files changed (50) hide show
  1. package/README.md +53 -52
  2. package/package.json +6 -8
  3. package/src/active-claims.js +5 -5
  4. package/src/agent-roster.js +2 -2
  5. package/src/audit/admission.js +14 -11
  6. package/src/audit/conflict-markers-rule.js +9 -9
  7. package/src/audit/grammar.js +21 -18
  8. package/src/audit/rule-builders.js +20 -19
  9. package/src/audit/rules.js +35 -32
  10. package/src/audit/scopes.js +35 -34
  11. package/src/audit/status-row.js +14 -15
  12. package/src/block-renderer.js +5 -4
  13. package/src/boot.js +10 -8
  14. package/src/budget-gate.js +39 -36
  15. package/src/budget.js +3 -3
  16. package/src/cli-definition.js +35 -32
  17. package/src/commands/audit.js +3 -3
  18. package/src/commands/boot.js +2 -2
  19. package/src/commands/claim.js +46 -40
  20. package/src/commands/curate.js +34 -31
  21. package/src/commands/fix.js +74 -70
  22. package/src/commands/inbox.js +2 -2
  23. package/src/commands/init.js +13 -8
  24. package/src/commands/ledger.js +12 -12
  25. package/src/commands/log.js +20 -18
  26. package/src/commands/memo.js +5 -2
  27. package/src/commands/product-mix.js +18 -17
  28. package/src/commands/refresh.js +25 -22
  29. package/src/commands/rotate.js +10 -9
  30. package/src/commands/sync.js +26 -17
  31. package/src/conflict-markers.js +21 -21
  32. package/src/constants.js +38 -34
  33. package/src/gitattributes.js +10 -9
  34. package/src/integrity.js +29 -27
  35. package/src/issue-list-renderer.js +24 -16
  36. package/src/lane-files.js +11 -10
  37. package/src/ledger/anchor.js +6 -6
  38. package/src/ledger/projection.js +35 -32
  39. package/src/ledger/reader.js +4 -4
  40. package/src/marker-scanner.js +3 -2
  41. package/src/sanitize.js +12 -11
  42. package/src/secret-gate.js +41 -40
  43. package/src/status.js +12 -11
  44. package/src/storyboard-skeleton.js +20 -18
  45. package/src/util/agent-flag.js +8 -8
  46. package/src/util/clock.js +1 -1
  47. package/src/util/wiki-dir.js +7 -7
  48. package/src/weekly-log.js +115 -101
  49. package/src/wiki-sync.js +411 -379
  50. package/bin/fit-wiki.js +0 -94
@@ -7,8 +7,8 @@ import { resolveProjectRoot } from "../util/wiki-dir.js";
7
7
  import { auditWiki } from "./audit.js";
8
8
 
9
9
  // The routing contract: a single open issue, addressed to the technical-writer,
10
- // holding the audit findings. The title is matched verbatim on every run so a
11
- // dirty wiki appends to one issue rather than opening a new one each day.
10
+ // that holds the audit findings. Every run matches the title verbatim, so a
11
+ // dirty wiki appends to one issue and does not open a new one each day.
12
12
  const LABEL = {
13
13
  name: "wiki-curation",
14
14
  color: "BFD4F2",
@@ -19,13 +19,14 @@ const TITLE = "Wiki curation: shared-state audit findings";
19
19
  // GitHub rejects an issue or comment body over 65536 characters. Keep the whole
20
20
  // body under a margin below that so the preamble, JSON fence, and truncation
21
21
  // notice always fit. When the findings overflow, the body carries the first N
22
- // that fit plus a count; the full list stays reproducible via `fit-wiki audit`.
22
+ // that fit plus a count. `gemba-wiki audit` still reproduces the full list.
23
23
  const MAX_BODY = 65000;
24
24
 
25
25
  /**
26
26
  * Compose the issue body from the audit's JSON findings. The findings ride a
27
- * fenced ```json block; the body is passed to `gh` via `--body-file` (a temp
28
- * file), never argv, so untrusted finding text cannot be misread as a flag.
27
+ * fenced ```json block. The caller passes the body to `gh` through
28
+ * `--body-file` (a temp file) and never through argv. So `gh` cannot misread
29
+ * untrusted finding text as a flag.
29
30
  * @param {string} findingsJson
30
31
  * @param {{shown?: number, total?: number}} [trunc]
31
32
  * @returns {string}
@@ -34,12 +35,12 @@ function buildBody(findingsJson, { shown, total } = {}) {
34
35
  const lines = [
35
36
  "Scheduled `curate-wiki` audit found shared-wiki violations.",
36
37
  "",
37
- "Owner: **technical-writer** (service these via the curation shift; the per-PR `wiki` gate no longer reads shared wiki state).",
38
+ "Owner: **technical-writer**. Service these through the curation shift. The per-PR `wiki` gate no longer reads shared wiki state.",
38
39
  "",
39
40
  ];
40
41
  if (total != null && shown != null && shown < total) {
41
42
  lines.push(
42
- `Showing ${shown} of ${total} findings — the body was truncated to fit GitHub's comment limit. Run \`fit-wiki audit\` for the full list.`,
43
+ `Showing ${shown} of ${total} findings. The body is truncated to fit GitHub's comment limit. Run \`gemba-wiki audit\` for the full list.`,
43
44
  "",
44
45
  );
45
46
  }
@@ -49,9 +50,10 @@ function buildBody(findingsJson, { shown, total } = {}) {
49
50
 
50
51
  /**
51
52
  * Build the largest postable body for the audit findings. The full findings
52
- * usually fit; when they don't, keep the first N `fail` findings that stay
53
- * under GitHub's body limit and label the body as truncated. The shrink steps
54
- * down proportionally to the overflow, so it converges in a couple of passes.
53
+ * usually fit. When they do not fit, keep the first N `fail` findings that
54
+ * stay under GitHub's body limit. Label the body as truncated. The shrink
55
+ * steps down proportionally to the overflow, so it converges in a couple of
56
+ * passes.
55
57
  * @param {{level: string}[]} findings
56
58
  * @returns {string}
57
59
  */
@@ -76,8 +78,8 @@ function fitBody(findings) {
76
78
 
77
79
  // Resolve the monorepo's `owner/repo` slug the way refresh.js/product-mix.js
78
80
  // do: an explicit FIT_GH_REPO override (sandbox proxy URLs), else the origin
79
- // remote parsed via the injected git client. Null lets `gh` fall back to its
80
- // own cwd resolution.
81
+ // remote parsed through the injected git client. Null lets `gh` fall back to
82
+ // its own cwd resolution.
81
83
  async function deriveRepo(gitClient, cwd, env) {
82
84
  if (env.FIT_GH_REPO) return env.FIT_GH_REPO;
83
85
  if (!gitClient) return null;
@@ -88,7 +90,7 @@ async function deriveRepo(gitClient, cwd, env) {
88
90
  }
89
91
  }
90
92
 
91
- // A missing token is non-fatal: `gh` may still resolve ambient auth.
93
+ // A missing token is non-fatal. `gh` may still resolve ambient auth.
92
94
  async function resolveToken() {
93
95
  try {
94
96
  return (await createScriptConfig("wiki")).ghToken();
@@ -98,8 +100,9 @@ async function resolveToken() {
98
100
  }
99
101
 
100
102
  /**
101
- * Find the open `wiki-curation` issue by its verbatim title, or null. Any
102
- * parse failure or empty result is treated as "no issue" (create path).
103
+ * Find the open `wiki-curation` issue by its verbatim title, or null. This
104
+ * function treats any parse failure or empty result as "no issue" (create
105
+ * path).
103
106
  * @param {import("@forwardimpact/libcli").InvocationContext["deps"]["runtime"]} runtime
104
107
  * @param {string[]} repoArgs
105
108
  * @param {{cwd: string, env: object}} opts
@@ -129,11 +132,11 @@ async function findOpenIssue(runtime, repoArgs, opts) {
129
132
  }
130
133
 
131
134
  /**
132
- * Route the composed body to the single `wiki-curation` issue: ensure the
133
- * label, find the open issue by title, then comment on it or create it. The
134
- * body goes through a temp file (never argv) so untrusted finding text cannot
135
- * be read as a flag. On a `gh` failure the reason is logged and `ok:false`
136
- * returned so the caller exits non-zero.
135
+ * Route the composed body to the single `wiki-curation` issue: make sure the
136
+ * label exists, find the open issue by title, then comment on it or create it.
137
+ * The body goes through a temp file and never through argv. So `gh` cannot
138
+ * read untrusted finding text as a flag. On a `gh` failure this function logs
139
+ * the reason and returns `ok:false`, so the caller exits non-zero.
137
140
  * @param {import("@forwardimpact/libcli").InvocationContext} ctx
138
141
  * @param {string} body
139
142
  * @param {ReturnType<typeof createLogger>} logger
@@ -150,8 +153,8 @@ async function routeFindings(ctx, body, logger) {
150
153
  : runtime.proc.env;
151
154
  const repoArgs = repo ? ["--repo", repo] : [];
152
155
 
153
- // Ensure the label exists; a re-create on an existing label exits non-zero,
154
- // which is expected and ignored.
156
+ // Make sure the label exists. A re-create on an existing label exits
157
+ // non-zero. That is expected, and the code ignores it.
155
158
  await runtime.subprocess.run(
156
159
  "gh",
157
160
  [
@@ -169,8 +172,8 @@ async function routeFindings(ctx, body, logger) {
169
172
 
170
173
  const number = await findOpenIssue(runtime, repoArgs, { cwd, env });
171
174
 
172
- // Pass the body through a temp file, not argv — robust to length and immune
173
- // to finding text being read as a flag.
175
+ // Pass the body through a temp file instead of argv. A temp file is robust
176
+ // to length, and `gh` cannot read finding text as a flag.
174
177
  const tmp = runtime.proc.env.RUNNER_TEMP || runtime.proc.env.TMPDIR || "/tmp";
175
178
  const bodyFile = path.join(tmp, "wiki-curation-body.md");
176
179
  runtime.fsSync.writeFileSync(bodyFile, body);
@@ -208,12 +211,12 @@ async function routeFindings(ctx, body, logger) {
208
211
  }
209
212
 
210
213
  /**
211
- * Audit the shared wiki and, when it is dirty, route the findings to the
212
- * single `wiki-curation` issue (create or comment) addressed to the
213
- * technical-writer. This is the SOLE home of the shared-wiki audit verdict; the
214
- * per-PR `wiki` gate no longer reads live wiki state. A clean wiki routes
215
- * nothing. The label/search/create-or-comment logic lives here, not in the
216
- * workflow, so the curation step is one CLI call.
214
+ * Audit the shared wiki. When it is dirty, route the findings to the single
215
+ * `wiki-curation` issue (create or comment) addressed to the technical-writer.
216
+ * This is the SOLE home of the shared-wiki audit verdict. The per-PR `wiki`
217
+ * gate no longer reads live wiki state. A clean wiki routes nothing. The
218
+ * label/search/create-or-comment logic lives here and not in the workflow, so
219
+ * the curation step is one CLI call.
217
220
  *
218
221
  * @param {import("@forwardimpact/libcli").InvocationContext} ctx
219
222
  * @returns {Promise<{ok: boolean}>}
@@ -224,7 +227,7 @@ export async function runCurateCommand(ctx) {
224
227
  const { findings } = auditWiki(ctx);
225
228
 
226
229
  if (!findings.some((f) => f.level === "fail")) {
227
- runtime.proc.stdout.write("wiki audit clean — no curation issue routed\n");
230
+ runtime.proc.stdout.write("wiki audit clean, no curation issue routed\n");
228
231
  return { ok: true };
229
232
  }
230
233
 
@@ -18,15 +18,15 @@ import { resolveProjectRoot } from "../util/wiki-dir.js";
18
18
  import { FAST_MODEL } from "@forwardimpact/libutil/models";
19
19
  import { createLogger } from "@forwardimpact/libtelemetry";
20
20
 
21
- // Pipeline: audit → deterministic rotation (the one fix needing a file seal the
22
- // agent can't do) → re-audit → Haiku agent on the prose-judgment residual →
23
- // flag what neither should touch. MAX_ROUNDS still caps the agent loop so an
24
- // unresolvable agent-class finding fails loudly rather than spinning forever.
21
+ // Pipeline: audit → deterministic rotation (the one fix that needs a file seal
22
+ // the agent cannot do) → re-audit → Haiku agent on the prose-judgment residual
23
+ // → flag what neither should touch. MAX_ROUNDS still caps the agent loop. An
24
+ // unresolvable agent-class finding fails loudly and does not loop forever.
25
25
  const MAX_ROUNDS = 3;
26
26
 
27
27
  /**
28
28
  * A finding's remediation class, from the declarative rule. Rules without a
29
- * `remediation` field default to `"agent"`: the Haiku agent handles all
29
+ * `remediation` field default to `"agent"`. The Haiku agent handles all
30
30
  * prose-judgment fixes (summary trims, section order, MEMORY.md prose).
31
31
  */
32
32
  function classOf(finding) {
@@ -34,14 +34,16 @@ function classOf(finding) {
34
34
  }
35
35
 
36
36
  /**
37
- * Every rule governing a scope with an open finding, as `id — hint` lines.
38
- * Handing the agent the full contract for the files it edits — not just the
39
- * failing rules — stops it fixing one finding by breaking another (dropping
40
- * the `**Last run**:` line, appending a section after `## Open Blockers`, …).
37
+ * Every rule that governs a scope with an open finding, as `id — hint` lines.
38
+ * The agent gets the full contract for the files it edits. The contract covers
39
+ * more than the rules that fail. It stops the agent from breaking one
40
+ * invariant while it fixes another finding (a dropped `**Last run**:` line, a
41
+ * section appended after `## Open Blockers`, …).
41
42
  *
42
- * Only static-string hints are listed: a function hint is a per-finding
43
- * resolved command (a `rotate` remediation the agent never performs), not a
44
- * file invariant, so listing it would leak its source text into the prompt.
43
+ * This lists only static-string hints. A function hint is a per-finding
44
+ * resolved command (a `rotate` remediation the agent never performs). A
45
+ * function hint is not a file invariant, so an entry for it would leak its
46
+ * source text into the prompt.
45
47
  */
46
48
  function invariantContract(findings) {
47
49
  const scopes = new Set(
@@ -53,52 +55,53 @@ function invariantContract(findings) {
53
55
  }
54
56
 
55
57
  /**
56
- * The opening task: the findings, the invariant contract, and the things the
57
- * rule hints don't cover — where trimmed history goes (only existing
58
- * weekly-log files; rotation owns minting new ones), and to prefer a single
59
- * Write.
58
+ * The opening task. It carries the findings, the invariant contract, and the
59
+ * things the rule hints do not cover. It says where trimmed history goes (only
60
+ * existing weekly-log files, because rotation mints the new ones). It also
61
+ * says to prefer a single Write.
60
62
  */
61
63
  function composeTask(findings, wikiRoot, projectRoot) {
62
64
  return [
63
- `Fix these wiki audit findings by editing files under ${wikiRoot}.`,
65
+ `Fix these wiki audit findings. Edit files under ${wikiRoot}.`,
64
66
  ``,
65
67
  emitFindingsText(findings, { cwd: projectRoot }),
66
68
  ``,
67
- `All of these invariants must hold when you finish — never fix one finding`,
68
- `by breaking another:`,
69
+ `All of these invariants must hold when you finish. Never fix one finding`,
70
+ `and break another:`,
69
71
  ...invariantContract(findings),
70
72
  ``,
71
73
  `Move history out of an over-budget summary into the agent's existing`,
72
- `weekly-log file or its current part (wiki/<agent>-YYYY-Www[-partN].md) —`,
73
- `never a new summary section, and never a new file: rotation tooling owns`,
74
- `when part files are created, so do not mint filenames yourself. If the`,
75
- `trimmed narrative already exists in the weekly log, replace it in the`,
76
- `summary with a pointer to that file instead of copying it anywhere.`,
74
+ `weekly-log file or its current part (wiki/<agent>-YYYY-Www[-partN].md).`,
75
+ `Never write a new summary section, and never a new file. The rotation`,
76
+ `tool owns when it creates part files, so do not mint filenames yourself.`,
77
+ `If the trimmed narrative already exists in the weekly log, replace it in`,
78
+ `the summary with a pointer to that file instead of copying it anywhere.`,
77
79
  `Prefer a single Write over many Edits.`,
78
80
  ].join("\n");
79
81
  }
80
82
 
81
- /** The resume task: the findings that survived the last edit. */
83
+ /** The resume task. It carries the findings that survived the last edit. */
82
84
  function composeFollowup(findings, projectRoot) {
83
85
  return [
84
- `The wiki still fails the audit. Remaining findings:`,
86
+ `The wiki still fails the audit. The findings that remain:`,
85
87
  ``,
86
88
  emitFindingsText(findings, { cwd: projectRoot }),
87
89
  ``,
88
- `Fix every one without breaking any invariant listed earlier.`,
90
+ `Fix every one. Do not break any invariant listed earlier.`,
89
91
  ].join("\n");
90
92
  }
91
93
 
92
94
  /**
93
- * Deterministic pre-pass: seal every over-budget current-week weekly-log main
94
- * file via `rotateIfOverBudget`. The agent name comes from the audit's own
95
- * subjects (keyed by path) — no filename parsing. `force: true` rotates even a
96
- * word-over/line-under file.
95
+ * Deterministic pre-pass. It seals every over-budget current-week main log
96
+ * with `rotateIfOverBudget`. The agent name comes from the audit's own
97
+ * subjects (keyed by path), so nothing parses the filename. `force: true`
98
+ * rotates even a word-over/line-under file.
97
99
  *
98
- * `rotateIfOverBudget` always seals the agent's *current-week* log, so we only
99
- * call it when the finding IS that file. A prior-week over-budget main is left
100
- * untouched (rotating it would force-seal a healthy current-week log instead);
101
- * it survives the re-audit and is flagged for a human.
100
+ * `rotateIfOverBudget` always seals the agent's *current-week* log, so we call
101
+ * it only when the finding IS that file. A prior-week over-budget main log
102
+ * stays untouched. A rotation would force-seal a healthy current-week log
103
+ * instead. The over-budget main survives the re-audit, and the run flags it
104
+ * for a human.
102
105
  */
103
106
  /**
104
107
  * Seal one over-budget current-week main log. A failed seal leaves the source
@@ -122,7 +125,7 @@ function sealMainLog(agent, { wikiRoot, today, projectRoot, fs, out, err }) {
122
125
  );
123
126
  }
124
127
  } catch (e) {
125
- err(`fit-wiki fix: rotate failed for ${agent}: ${e.message}\n`);
128
+ err(`gemba-wiki fix: rotate failed for ${agent}: ${e.message}\n`);
126
129
  }
127
130
  }
128
131
 
@@ -133,8 +136,8 @@ function rotateOverBudgetMainLogs(findings, deps) {
133
136
  ];
134
137
  const agentByPath = new Map(subjects.map((s) => [s.path, s.agentPrefix]));
135
138
  // A log over BOTH budgets yields two `rotate` findings with the same path
136
- // (different rule ids); seal each path once, or the second force call would
137
- // bisect the freshly-written fresh-main into a spurious near-empty part.
139
+ // (different rule ids). Seal each path once. A second force call would
140
+ // bisect the newly written main file into a spurious near-empty part.
138
141
  const sealed = new Set();
139
142
  for (const f of findings) {
140
143
  if (classOf(f) !== "rotate") continue;
@@ -147,9 +150,9 @@ function rotateOverBudgetMainLogs(findings, deps) {
147
150
  }
148
151
 
149
152
  /**
150
- * Re-bisect one over-budget sealed part, logging each new sibling slot it
151
- * produces (the reused source slot is not a new file). A failed reseal leaves
152
- * the source intact (the writer rolled back), so the re-audit re-flags it.
153
+ * Re-bisect one over-budget sealed part. Log each new sibling slot it produces
154
+ * (the reused source slot is not a new file). A failed reseal leaves the
155
+ * source intact (the writer rolled back), so the re-audit re-flags it.
153
156
  */
154
157
  function resealPart(partPath, { fs, projectRoot, out, err }) {
155
158
  try {
@@ -164,22 +167,22 @@ function resealPart(partPath, { fs, projectRoot, out, err }) {
164
167
  }
165
168
  } catch (e) {
166
169
  err(
167
- `fit-wiki fix: rebisect failed for ` +
170
+ `gemba-wiki fix: rebisect failed for ` +
168
171
  `${path.relative(projectRoot, partPath)}: ${e.message}\n`,
169
172
  );
170
173
  }
171
174
  }
172
175
 
173
176
  /**
174
- * Deterministic pass for over-budget sealed parts: re-bisect each at its
175
- * day-section seams. A part with no splittable seam is left byte-identical (the
176
- * re-audit re-flags it for a human). Agent and week come from the part filename,
177
- * so no subject lookup is needed. Part findings are disjoint from the main-log
178
- * findings `rotateOverBudgetMainLogs` handles.
177
+ * Deterministic pass for over-budget sealed parts. It re-bisects each part at
178
+ * its day-section seams. A part with no splittable seam stays byte-identical
179
+ * (the re-audit re-flags it for a human). Agent and week come from the part
180
+ * filename, so the pass needs no subject lookup. Part findings are disjoint
181
+ * from the main-log findings `rotateOverBudgetMainLogs` handles.
179
182
  */
180
183
  function rebisectOverBudgetParts(findings, deps) {
181
- // A part over both budgets yields two findings with the same path; re-bisect
182
- // each path once.
184
+ // A part over both budgets yields two findings with the same path.
185
+ // Re-bisect each path once.
183
186
  const done = new Set();
184
187
  for (const f of findings) {
185
188
  if (classOf(f) !== "rotate") continue;
@@ -190,29 +193,29 @@ function rebisectOverBudgetParts(findings, deps) {
190
193
  }
191
194
  }
192
195
 
193
- /** Report findings that need human judgment — never auto-fixed. */
196
+ /** Report findings that need human judgment. Nothing auto-fixes them. */
194
197
  function reportFlags(err, flagFindings, projectRoot) {
195
198
  err(
196
- `fit-wiki fix: ${flagFindings.length} finding(s) need human judgment ` +
199
+ `gemba-wiki fix: ${flagFindings.length} finding(s) need human judgment ` +
197
200
  `(not auto-fixable):\n` +
198
201
  emitFindingsText(flagFindings, { cwd: projectRoot }),
199
202
  );
200
203
  }
201
204
 
202
205
  /**
203
- * Surface a round's agent error, if any. Returns true when it is fatal: a
204
- * missing sessionId means the process never started (e.g. the SDK refused
206
+ * Surface a round's agent error, if any. Returns true when the error is fatal.
207
+ * A missing sessionId means the process never started (e.g. the SDK refused
205
208
  * bypass-permissions as root), so there is nothing to resume. A turn-limit or
206
- * transient error keeps its session and may have made partial progress, so it
207
- * is noted but not fatal — the re-audit decides.
209
+ * transient error keeps its session and can make partial progress. The
210
+ * function notes it and does not treat it as fatal. The re-audit decides.
208
211
  */
209
212
  function isFatalError(result, round, err) {
210
213
  if (!result.error) return false;
211
214
  if (!result.sessionId) {
212
- err(`fit-wiki fix: agent run failed: ${result.error.message}\n`);
215
+ err(`gemba-wiki fix: agent run failed: ${result.error.message}\n`);
213
216
  return true;
214
217
  }
215
- err(`fit-wiki fix: round ${round} agent error: ${result.error.message}\n`);
218
+ err(`gemba-wiki fix: round ${round} agent error: ${result.error.message}\n`);
216
219
  return false;
217
220
  }
218
221
 
@@ -237,10 +240,10 @@ async function buildFixRunner(ctx, projectRoot, runtime) {
237
240
  }
238
241
 
239
242
  /**
240
- * Run the agent on the prose-judgment findings, re-auditing each round until
241
- * clean, flag-only, or MAX_ROUNDS is exhausted. The audit is the verdict, not
242
- * the agent's self-report; resuming extends the turn budget for a trim too
243
- * large for one round.
243
+ * Run the agent on the prose-judgment findings. Re-audit each round until the
244
+ * wiki is clean, until only flags remain, or until MAX_ROUNDS is exhausted.
245
+ * The audit gives the verdict. The agent's self-report does not. A resume
246
+ * extends the turn budget for a trim too large for one round.
244
247
  */
245
248
  async function runAgentRounds(runner, agentFindings, deps) {
246
249
  const { wikiRoot, projectRoot, audit, partition, out, err } = deps;
@@ -265,7 +268,7 @@ async function runAgentRounds(runner, agentFindings, deps) {
265
268
  }
266
269
 
267
270
  err(
268
- `fit-wiki fix: ${agentFindings.length} finding(s) remain after ` +
271
+ `gemba-wiki fix: ${agentFindings.length} finding(s) remain after ` +
269
272
  `${MAX_ROUNDS} round(s):\n` +
270
273
  emitFindingsText(agentFindings, { cwd: projectRoot }),
271
274
  );
@@ -291,10 +294,10 @@ export async function runFixCommand(ctx) {
291
294
  buildContext({ wikiRoot, today, fs, subprocess: runtime.subprocess }),
292
295
  { resolveScope },
293
296
  );
294
- // The agent only ever gets prose-judgment (`agent`-class) findings. A
295
- // `rotate` finding that survived the pre-pass (e.g. a prior-week log) is
296
- // unfixable by the agent — and trimming append-only history to satisfy a
297
- // budget would corrupt it — so it joins the flag set for a human.
297
+ // The agent only ever gets prose-judgment (`agent`-class) findings. The
298
+ // agent cannot fix a `rotate` finding that survived the pre-pass (e.g. a
299
+ // prior-week log). A trim of append-only history to satisfy a budget would
300
+ // corrupt the history. So the finding joins the flag set for a human.
298
301
  const partition = (found) => ({
299
302
  agentFindings: found.filter((f) => classOf(f) === "agent"),
300
303
  flagFindings: found.filter((f) => classOf(f) !== "agent"),
@@ -307,7 +310,7 @@ export async function runFixCommand(ctx) {
307
310
  }
308
311
 
309
312
  // Deterministic layer: seal over-budget main logs, then re-bisect over-budget
310
- // sealed parts. Both are content-preserving — no agent, no history rewrite.
313
+ // sealed parts. Both preserve content, with no agent and no history rewrite.
311
314
  if (findings.some((f) => classOf(f) === "rotate")) {
312
315
  const rotateDeps = { wikiRoot, today, projectRoot, fs, out, err };
313
316
  rotateOverBudgetMainLogs(findings, rotateDeps);
@@ -319,15 +322,16 @@ export async function runFixCommand(ctx) {
319
322
  }
320
323
  }
321
324
 
322
- // Residual: agent-class goes to the writer; everything else (flag, plus any
323
- // rotate finding the deterministic pass could not handle) needs a human.
325
+ // Residual: agent-class goes to the writer. Everything else needs a human
326
+ // (flag, plus any rotate finding the deterministic pass could not handle).
324
327
  const { agentFindings, flagFindings } = partition(findings);
325
328
  if (agentFindings.length === 0) {
326
329
  reportFlags(err, flagFindings, projectRoot);
327
330
  return { ok: false, code: 2 };
328
331
  }
329
332
 
330
- // Constructed only now, so a rotation-only or flag-only run never spawns it.
333
+ // Build the runner only now, so a rotation-only or flag-only run never
334
+ // spawns it.
331
335
  const runner = await buildFixRunner(ctx, projectRoot, runtime);
332
336
  return runAgentRounds(runner, agentFindings, {
333
337
  wikiRoot,
@@ -13,7 +13,7 @@ function paths(runtime, options) {
13
13
  const wikiRoot = resolveWikiRoot(runtime, options);
14
14
  const resolved = requireAgentFlag(options, {
15
15
  command: "inbox",
16
- example: "fit-wiki inbox list --agent staff-engineer",
16
+ example: "gemba-wiki inbox list --agent staff-engineer",
17
17
  });
18
18
  if (!resolved.ok) return { error: resolved };
19
19
  const agent = resolved.agent;
@@ -104,7 +104,7 @@ function appendPriorityRow(memoryText, { item, agents, owner, status, added }) {
104
104
  ];
105
105
  return memoryText.replace(/\n*$/, "") + "\n" + block.join("\n");
106
106
  }
107
- // Find last data row.
107
+ // Find the last data row.
108
108
  let sepIdx = -1;
109
109
  for (let i = headingIdx + 1; i < lines.length; i++) {
110
110
  if (/^\|\s*---/.test(lines[i])) {
@@ -9,7 +9,12 @@ import {
9
9
  ACTIVE_CLAIMS_TABLE_SEPARATOR,
10
10
  } from "../constants.js";
11
11
 
12
- /** Resolve the wiki clone URL. Honors the FIT_WIKI_URL env var as an explicit override (for sandboxed environments where `origin` is rewritten to a local proxy that does not serve wiki repos); otherwise derives the URL by appending `.wiki.git` to the parent repo's `origin` remote. */
12
+ /**
13
+ * Resolve the wiki clone URL. The FIT_WIKI_URL env var is an explicit
14
+ * override. Use it in a sandboxed environment where a local proxy replaces
15
+ * `origin` and does not serve wiki repos. Without that override, the function
16
+ * appends `.wiki.git` to the parent repo's `origin` remote.
17
+ */
13
18
  export async function deriveWikiUrl(gitClient, parentDir, env) {
14
19
  if (env.FIT_WIKI_URL) return env.FIT_WIKI_URL;
15
20
  try {
@@ -31,8 +36,8 @@ function scaffoldActiveClaims(runtime, memoryPath) {
31
36
  "",
32
37
  ACTIVE_CLAIMS_HEADING,
33
38
  "",
34
- "In-flight work claimed by an agent. Row present = active; row absent = settled.",
35
- "Writers: `fit-wiki claim`, `fit-wiki release`. Reader: `fit-wiki boot`.",
39
+ "In-flight work claimed by an agent. Row present = active. Row absent = settled.",
40
+ "Writers: `gemba-wiki claim`, `gemba-wiki release`. Reader: `gemba-wiki boot`.",
36
41
  "",
37
42
  ACTIVE_CLAIMS_TABLE_HEADER,
38
43
  ACTIVE_CLAIMS_TABLE_SEPARATOR,
@@ -65,14 +70,14 @@ async function maybeCloneWiki(wikiSync, gitClient, projectRoot, runtime) {
65
70
  if (cloneResult.cloned) {
66
71
  await wikiSync.inheritIdentity();
67
72
  } else {
68
- logger.warn(
69
- "init",
70
- "could not clone wiki, continuing with local-only steps",
71
- );
73
+ logger.warn("init", "could not clone wiki, so only the local steps run");
72
74
  }
73
75
  }
74
76
 
75
- /** Clone the wiki if not already present, scaffold Active Claims in MEMORY.md, and create per-skill metric directories. */
77
+ /**
78
+ * Clone the wiki if it is absent. Scaffold Active Claims in MEMORY.md. Create
79
+ * a metric directory for each skill.
80
+ */
76
81
  export async function runInitCommand(ctx) {
77
82
  const { runtime, wikiSync, gitClient } = ctx.deps;
78
83
  const options = ctx.options;
@@ -38,7 +38,7 @@ function nextFreeIds(fold, kind, count) {
38
38
  return ids;
39
39
  }
40
40
 
41
- /** Read the ordered anchor sequence and fold it, resolving the repo slug. */
41
+ /** Read the ordered anchor sequence, fold it, and resolve the repo slug. */
42
42
  async function loadFold({ gitClient, ghClient, wikiDir, issue }) {
43
43
  const url = await gitClient.remoteGetUrl("origin", { cwd: wikiDir });
44
44
  const { owner, repo } = parseOwnerRepo(url);
@@ -70,10 +70,10 @@ async function allocate(env, options) {
70
70
  };
71
71
  }
72
72
  const { fold, owner, repo } = await loadFold(env);
73
- // Backfill registers an anchor for ids that predate the anchor surface, named
74
- // explicitly via --ids; their event keys already exist in history. A plain
75
- // allocate mints the next free ids of the kind. The conflict detector at
76
- // rebuild guards against double-registering an id that already has an anchor.
73
+ // Backfill registers an anchor for ids that predate the anchor surface. The
74
+ // --ids flag names them, and their event keys already exist in history. A
75
+ // plain allocate mints the next free ids of the kind. The conflict detector
76
+ // at rebuild guards against an id that already has an anchor.
77
77
  let ids;
78
78
  if (options.ids) {
79
79
  ids = options.ids
@@ -93,8 +93,8 @@ async function allocate(env, options) {
93
93
  ids = nextFreeIds(fold, kind, count);
94
94
  }
95
95
  const body = renderAnchorBody({ kind, ids, event, note: options.note ?? "" });
96
- // The anchor publication is the allocation; no projection is written here.
97
- // The printed ids are provisional — a rebuild over the published sequence is
96
+ // The anchor publication is the allocation. This code writes no projection.
97
+ // The printed ids are provisional. A rebuild over the published sequence is
98
98
  // authoritative and resolves any concurrent interleave first-published-wins.
99
99
  await ghClient.apiPost(
100
100
  `repos/${owner}/${repo}/issues/${env.issue}/comments`,
@@ -118,7 +118,7 @@ function readLedgerPage(runtime, wikiDir) {
118
118
  : "";
119
119
  }
120
120
 
121
- /** Project the anchor record onto the ledger-page body, preserving cited prose. */
121
+ /** Project the anchor record onto the ledger-page body. Keep cited prose. */
122
122
  async function project(env, options) {
123
123
  const { runtime, wikiDir } = env;
124
124
  const labelMode = options.gapped ? "gapped" : "renumber";
@@ -181,11 +181,11 @@ async function verify(env, options) {
181
181
  const SUBS = { allocate, rebuild, verify };
182
182
 
183
183
  /**
184
- * `fit-wiki ledger <allocate|rebuild|verify>` — the allocation procedure that
184
+ * `gemba-wiki ledger <allocate|rebuild|verify>` — the allocation procedure that
185
185
  * keeps identity off the merge-contested page. Allocation publishes an anchor
186
- * comment to the obstacle issue with no projection write at allocation time;
187
- * rebuild and verify project the anchor record onto the ledger page and MEMORY
188
- * row, preserving anchor-cited prose.
186
+ * comment to the obstacle issue and writes no projection at allocation time.
187
+ * Rebuild and verify project the anchor record onto the ledger page and MEMORY
188
+ * row. Both keep anchor-cited prose.
189
189
  */
190
190
  export async function runLedgerCommand(ctx) {
191
191
  const { runtime, gitClient, ghClient } = ctx.deps;