@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
@@ -1,14 +1,14 @@
1
1
  // Post-landing, pre-push budget re-validation on the size (word/line) axis.
2
2
  //
3
3
  // The wiki landing flow re-runs the audit's budget predicates over the
4
- // outgoing tree between landing and push, and refuses a push that introduces
4
+ // outgoing tree between landing and push. It refuses a push that introduces
5
5
  // or deepens a per-file budget breach this writer's push would publish. The
6
- // gate reuses the audit's budget rules by reference: it resolves the rule
7
- // objects named by `BUDGET_RULE_IDS` and calls each rule's own `check` (the
8
- // over-cap predicate) plus the same `countWords` / `countLines` the audit
9
- // builds its subjects from. It never re-defines a budget, never routes through
10
- // the `runRules` engine (which drops the numeric value and emits nothing under
11
- // cap), and never edits — it refuses, keeping commits local.
6
+ // gate reuses the audit's budget rules by reference. It resolves the rule
7
+ // objects named by `BUDGET_RULE_IDS`. It then calls each rule's own `check`
8
+ // (the over-cap predicate) plus the same `countWords` / `countLines` the audit
9
+ // builds its subjects from. It never re-defines a budget. It never routes
10
+ // through the `runRules` engine, which drops the numeric value and emits
11
+ // nothing under cap. It never edits. It refuses, which keeps commits local.
12
12
 
13
13
  import path from "node:path";
14
14
  import { BUDGET_RULE_IDS, RULES } from "./audit/rules.js";
@@ -16,9 +16,10 @@ import { buildContext, resolveScope } from "./audit/scopes.js";
16
16
  import { countLines, countWords } from "./budget.js";
17
17
 
18
18
  /**
19
- * Resolve `BUDGET_RULE_IDS` to their rule objects in `RULES`, tagging each with
20
- * the count axis its id implies. Throws if a named id is missing from `RULES`,
21
- * so a rule rename surfaces here rather than silently dropping a predicate.
19
+ * Resolve `BUDGET_RULE_IDS` to their rule objects in `RULES`. Tag each one with
20
+ * the count axis its id implies. It throws if a named id is missing from
21
+ * `RULES`, so a rule rename surfaces here and does not silently drop a
22
+ * predicate.
22
23
  * @returns {Array<{id: string, scope: string, axis: 'words'|'lines', check: Function}>}
23
24
  */
24
25
  export function budgetRules() {
@@ -36,10 +37,10 @@ export function budgetRules() {
36
37
  }
37
38
 
38
39
  /**
39
- * Enumerate which wiki files are budgeted, by reusing the audit's
40
- * classification. Subjects carry an absolute `path`, so each is reduced to the
41
- * `<file>` half of `git show <ref>:<file>` relative to `wikiRoot`. No count is
42
- * read off the working-dir subject — only the file identity and its scope.
40
+ * Enumerate which wiki files are budgeted. Reuse the audit's classification.
41
+ * Subjects carry an absolute `path`. Reduce each one to the `<file>` half of
42
+ * `git show <ref>:<file>`, relative to `wikiRoot`. This function reads no count
43
+ * off the working-dir subject. It reads only the file identity and its scope.
43
44
  * @param {object} ctx - An audit context from `buildContext`.
44
45
  * @param {string} wikiRoot - The wiki clone directory the paths are relative to.
45
46
  * @returns {Array<{relPath: string, scope: string}>}
@@ -56,12 +57,12 @@ export function budgetedFiles(ctx, wikiRoot) {
56
57
  }
57
58
 
58
59
  /**
59
- * Measure the budget predicates for the tree at `ref`. Reads each budgeted
60
- * file's blob via the cwd-bound `showFile`, counts it once with the audit's
61
- * counters, then for every budget rule on that file's scope records the axis
60
+ * Measure the budget predicates for the tree at `ref`. Read each budgeted
61
+ * file's blob through the cwd-bound `showFile`. Count it once with the audit's
62
+ * counters. Then, for every budget rule on that file's scope, record the axis
62
63
  * value and whether the rule's own `check` flags it over cap. An absent path
63
- * at the ref counts as 0 (matching the audit's "missing counts as empty"
64
- * posture); an unreadable ref makes `showFile` throw, which propagates.
64
+ * at the ref counts as 0. This matches the audit's "missing counts as empty"
65
+ * posture. An unreadable ref makes `showFile` throw, and the throw propagates.
65
66
  * @param {(ref: string, file: string) => Promise<string|null>} showFile
66
67
  * @param {string} ref - The tree-ish to measure (e.g. "HEAD", a SHA).
67
68
  * @param {Array<{relPath: string, scope: string}>} budgeted
@@ -88,15 +89,16 @@ export async function measureRef(showFile, ref, budgeted) {
88
89
  }
89
90
 
90
91
  /**
91
- * Compare the outgoing tree against the two push-input baselines and return the
92
+ * Compare the outgoing tree against the two push-input baselines. Return the
92
93
  * per-file/per-predicate refusal delta. For each (file, rule) the baseline is
93
- * the worse (higher) of the session-base and origin-tip values, treating an
94
- * absent measurement as 0. A predicate refuses iff the outgoing value is over
95
- * cap AND strictly exceeds that baseline — so equal-or-better states pass, and
96
- * a foreign breach the writer did not worsen passes. A `summary.*` breach on a
97
- * file listed in `exemptSummaryFiles` is surfaced instead of refused — the
98
- * memo-delivery seam, where blocking a delivery into deficient headroom would
99
- * enforce a contradiction the memo-headroom measures exist to resolve.
94
+ * the worse (higher) of the session-base and origin-tip values. An absent
95
+ * measurement counts as 0. A predicate refuses iff the outgoing value is over
96
+ * cap AND strictly exceeds that baseline. So equal-or-better states pass, and
97
+ * a foreign breach the writer did not worsen passes. The gate surfaces a
98
+ * `summary.*` breach on a file listed in `exemptSummaryFiles` instead of
99
+ * refusing it. Those files are the memo-delivery seam. A block on a delivery
100
+ * into deficient headroom would enforce a contradiction. The memo-headroom
101
+ * measures exist to resolve that contradiction.
100
102
  *
101
103
  * @param {object} args
102
104
  * @param {Map<string, Map<string, {value: number, overCap: boolean}>>} args.outgoing
@@ -137,14 +139,14 @@ export function revalidateBudgets({
137
139
  }
138
140
 
139
141
  /**
140
- * Run the gate end to end over the outgoing tree. Builds the audit context,
141
- * enumerates the budgeted files, measures the committed `HEAD` (what publishes)
142
- * and the two push-input baselines through the one `measureRef` path, then
143
- * computes the per-file/per-predicate delta. An unreadable baseline ref makes
144
- * `showFile` throw, which aborts the gate WITHOUT refusing — the gate only
145
- * refuses a regression it can prove, so a read failure surfaces (the push
146
- * proceeds) rather than fabricating a value-0 baseline that would wrongly block
147
- * a foreign pre-existing breach.
142
+ * Run the gate end to end over the outgoing tree. Build the audit context.
143
+ * Enumerate the budgeted files. Measure the committed `HEAD` (what publishes)
144
+ * and the two push-input baselines through the one `measureRef` path. Then
145
+ * compute the per-file/per-predicate delta. An unreadable baseline ref makes
146
+ * `showFile` throw, which aborts the gate WITHOUT refusing. The gate only
147
+ * refuses a regression it can prove. So a read failure surfaces and the push
148
+ * proceeds. The gate does not fabricate a value-0 baseline that would wrongly
149
+ * block a foreign pre-existing breach.
148
150
  *
149
151
  * @param {object} args
150
152
  * @param {(ref: string, file: string) => Promise<string|null>} args.showFile
@@ -181,7 +183,8 @@ export async function runBudgetGate({
181
183
  }
182
184
  originTip = await measureRef(showFile, originRef, budgeted);
183
185
  } catch {
184
- // Cannot prove a regression (unreadable ref) ⇒ do not refuse; fail-visible.
186
+ // Cannot prove a regression (unreadable ref) ⇒ do not refuse. This is the
187
+ // fail-visible posture.
185
188
  return { refusals: [], surfaced: [] };
186
189
  }
187
190
  return revalidateBudgets({
package/src/budget.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // Canonical line- and word-counters for the budgeted wiki surfaces. The audit
2
2
  // (`audit/scopes.js`) and the rotation primitive's bisecting seal
3
- // (`weekly-log.js`) both import this one pair so a part the seal calls
4
- // conforming cannot later be flagged by an audit counting differently.
3
+ // (`weekly-log.js`) both import this one pair. So no audit that counts
4
+ // differently can later flag a part the seal accepts as conforming.
5
5
 
6
- /** Count lines, not counting a trailing newline as an empty final line. */
6
+ /** Count lines. Do not count a trailing newline as an empty final line. */
7
7
  export function countLines(text) {
8
8
  return text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
9
9
  }
@@ -14,13 +14,13 @@ import { runFixCommand } from "./commands/fix.js";
14
14
  import { runLedgerCommand } from "./commands/ledger.js";
15
15
 
16
16
  /**
17
- * Build the `fit-wiki` libcli definition. Agent identity is never resolved from
18
- * the environment: agent-scoped subcommands require an explicit `--agent`
19
- * (`--from` for `memo`) and fail closed without it, so this module carries no
20
- * ambient agent identity. The version is resolved by libcli's `createCli` from
21
- * the bin's `packageJsonUrl`. Each subcommand carries a `handler` and (for
22
- * subcommand-bearing commands) `args`/`argsUsage` so `cli.dispatch` can route to
23
- * the per-command handler with a frozen `ctx`.
17
+ * Build the `gemba-wiki` libcli definition. This module never resolves agent
18
+ * identity from the environment. Agent-scoped subcommands require an explicit
19
+ * `--agent` (`--from` for `memo`). They fail closed without it, so this module
20
+ * carries no ambient agent identity. libcli's `createCli` resolves the version
21
+ * from the bin's `packageJsonUrl`. Each subcommand carries a `handler`. A
22
+ * command that takes subcommands also carries `args`/`argsUsage`, so
23
+ * `cli.dispatch` can route to the per-command handler with a frozen `ctx`.
24
24
  *
25
25
  * @returns {object} The libcli definition.
26
26
  */
@@ -47,13 +47,13 @@ export function createDefinition() {
47
47
  };
48
48
 
49
49
  return {
50
- name: "fit-wiki",
51
- description: "Wiki lifecycle management for the Kata agent system",
50
+ name: "gemba-wiki",
51
+ description: "Manage the wiki lifecycle for the Kata agent system",
52
52
  commands: [
53
53
  {
54
54
  name: "boot",
55
55
  description:
56
- "Print on-boot digest (priorities, claims, storyboard items) as JSON",
56
+ "Print the on-boot digest (priorities, claims, storyboard items) as JSON",
57
57
  handler: runBootCommand,
58
58
  options: {
59
59
  ...agentOpt,
@@ -101,9 +101,12 @@ export function createDefinition() {
101
101
  ...todayOpt,
102
102
  target: {
103
103
  type: "string",
104
- description: "What is being claimed (spec id, PR id, etc.)",
104
+ description: "Target to claim (spec id, PR id, etc.)",
105
+ },
106
+ branch: {
107
+ type: "string",
108
+ description: "Branch that carries the work",
105
109
  },
106
- branch: { type: "string", description: "Branch carrying the work" },
107
110
  pr: { type: "string", description: "Optional PR id" },
108
111
  "expires-at": {
109
112
  type: "string",
@@ -142,7 +145,7 @@ export function createDefinition() {
142
145
  },
143
146
  owner: {
144
147
  type: "string",
145
- description: "Owner field when promoting (default: --agent)",
148
+ description: "Owner field for promote (default: --agent)",
146
149
  },
147
150
  },
148
151
  },
@@ -190,7 +193,7 @@ export function createDefinition() {
190
193
  "dry-run": {
191
194
  type: "boolean",
192
195
  description:
193
- "Print the issue body and intended action without calling gh",
196
+ "Print the issue body and intended action. Do not call gh",
194
197
  },
195
198
  },
196
199
  },
@@ -217,7 +220,7 @@ export function createDefinition() {
217
220
  to: {
218
221
  type: "string",
219
222
  description:
220
- 'Target agent name, or "all" to broadcast (sender is skipped)',
223
+ 'Target agent name, or "all" to broadcast (skips the sender)',
221
224
  },
222
225
  message: {
223
226
  type: "string",
@@ -287,7 +290,7 @@ export function createDefinition() {
287
290
  type: "string",
288
291
  multiple: true,
289
292
  description:
290
- "Pathspec(s) limiting the write-set; omit to land the session's dirty set",
293
+ "Pathspec(s) that limit the write-set. Omit to land the session's dirty set",
291
294
  },
292
295
  },
293
296
  },
@@ -330,7 +333,7 @@ export function createDefinition() {
330
333
  gapped: {
331
334
  type: "boolean",
332
335
  description:
333
- "Render double-allocation losers as a gap, not a renumber",
336
+ "Render double-allocation losers as a gap instead of a renumber",
334
337
  },
335
338
  issue: {
336
339
  type: "string",
@@ -348,21 +351,21 @@ export function createDefinition() {
348
351
  },
349
352
  },
350
353
  examples: [
351
- "fit-wiki boot --agent staff-engineer",
352
- 'fit-wiki log decision --agent staff-engineer --surveyed "..." --chosen "..." --rationale "..."',
353
- "fit-wiki claim --agent staff-engineer --target spec-NNNN --branch claude/...",
354
- "fit-wiki release --agent staff-engineer --target spec-NNNN",
355
- "fit-wiki inbox list --agent staff-engineer",
356
- "fit-wiki rotate --agent staff-engineer",
357
- "fit-wiki audit",
358
- "fit-wiki curate",
359
- "fit-wiki fix",
360
- 'fit-wiki memo --from staff-engineer --to security-engineer --message "audit d642ff0c"',
361
- "fit-wiki refresh",
362
- "fit-wiki product-mix",
363
- "fit-wiki init",
364
- "fit-wiki push",
365
- "fit-wiki pull",
354
+ "gemba-wiki boot --agent staff-engineer",
355
+ 'gemba-wiki log decision --agent staff-engineer --surveyed "..." --chosen "..." --rationale "..."',
356
+ "gemba-wiki claim --agent staff-engineer --target spec-NNNN --branch claude/...",
357
+ "gemba-wiki release --agent staff-engineer --target spec-NNNN",
358
+ "gemba-wiki inbox list --agent staff-engineer",
359
+ "gemba-wiki rotate --agent staff-engineer",
360
+ "gemba-wiki audit",
361
+ "gemba-wiki curate",
362
+ "gemba-wiki fix",
363
+ 'gemba-wiki memo --from staff-engineer --to security-engineer --message "audit d642ff0c"',
364
+ "gemba-wiki refresh",
365
+ "gemba-wiki product-mix",
366
+ "gemba-wiki init",
367
+ "gemba-wiki push",
368
+ "gemba-wiki pull",
366
369
  ],
367
370
  documentation: [
368
371
  {
@@ -11,8 +11,8 @@ import { resolveProjectRoot } from "../util/wiki-dir.js";
11
11
 
12
12
  /**
13
13
  * Run the wiki audit and return its findings plus the resolved project root.
14
- * Shared by `runAuditCommand` (emits them) and `runCurateCommand` (routes
15
- * them to an issue) so the two cannot drift.
14
+ * `runAuditCommand` emits the findings. `runCurateCommand` routes them to an
15
+ * issue. Both share this function, so the two cannot drift.
16
16
  * @param {import("@forwardimpact/libcli").InvocationContext} ctx
17
17
  * @returns {{ findings: object[], projectRoot: string }}
18
18
  */
@@ -32,7 +32,7 @@ export function auditWiki(ctx) {
32
32
  return { findings: runRules(RULES, auditCtx, { resolveScope }), projectRoot };
33
33
  }
34
34
 
35
- /** Run the wiki audit and emit findings. JSON via --format json. */
35
+ /** Run the wiki audit and emit findings. Use --format json for JSON. */
36
36
  export function runAuditCommand(ctx) {
37
37
  const { runtime } = ctx.deps;
38
38
  const options = ctx.options;
@@ -40,13 +40,13 @@ function renderMarkdown(digest) {
40
40
  return lines.join("\n");
41
41
  }
42
42
 
43
- /** Print the on-boot digest for the calling agent. JSON by default; --format markdown renders prose. */
43
+ /** Print the on-boot digest for the agent that runs it. JSON by default. --format markdown renders prose. */
44
44
  export function runBootCommand(ctx) {
45
45
  const { runtime } = ctx.deps;
46
46
  const options = ctx.options;
47
47
  const resolved = requireAgentFlag(options, {
48
48
  command: "boot",
49
- example: "fit-wiki boot --agent staff-engineer",
49
+ example: "gemba-wiki boot --agent staff-engineer",
50
50
  });
51
51
  if (!resolved.ok) return resolved;
52
52
  const agent = resolved.agent;
@@ -24,9 +24,9 @@ const NOT_PUBLISHED = {
24
24
  };
25
25
 
26
26
  // Failure reasons that, on the claim/release surfaces, are an unsafe-state
27
- // refusal (D7/D9 family) rather than a saved-locally success (D1): the refusal
28
- // fires before the local write is publishable, or leaves the tree unsafe for a
29
- // later whole-tree sweep, so the surface must exit non-zero.
27
+ // refusal (D7/D9 family) rather than a saved-locally success (D1). The refusal
28
+ // fires before the local write is publishable. Or it leaves the tree unsafe
29
+ // for a later whole-tree sweep. So the surface must exit non-zero.
30
30
  const UNSAFE_STATE_REASONS = new Set([
31
31
  PUSH_REASONS.PRECONDITION,
32
32
  PUSH_REASONS.RESIDUE_CONFLICT,
@@ -37,7 +37,7 @@ const UNSAFE_STATE_REASONS = new Set([
37
37
  function notPublishedMessage(err) {
38
38
  return (
39
39
  `${err.message}\n` +
40
- "The row was written to MEMORY.md but is NOT published — it remains an " +
40
+ "The row is in MEMORY.md but is NOT published. It remains an " +
41
41
  "uncommitted working-tree change.\n"
42
42
  );
43
43
  }
@@ -52,23 +52,25 @@ function memoryPath(runtime, options) {
52
52
  }
53
53
 
54
54
  /**
55
- * Push the claim/release MEMORY.md change and translate the honest outcome
56
- * (the honest-CLI contract) into a command envelope, composed with the singleton merge
57
- * discipline (the singleton merge discipline) and the secret/ancestry guards:
58
- * - landed (grounded or re-applied) ⇒ `{ ok: true }`, success message printed;
55
+ * Push the claim/release MEMORY.md change. Translate the honest outcome (the
56
+ * honest-CLI contract) into a command envelope. Compose it with the singleton
57
+ * merge discipline (the singleton merge discipline) and the secret/ancestry
58
+ * guards:
59
+ * - landed (grounded or re-applied) ⇒ `{ ok: true }` with a printed success
60
+ * message;
59
61
  * - `rejected`/`transport` ⇒ `{ ok: true }` with a saved-locally warning (the
60
- * landed-locally row is complete; the session-end push is its retry);
62
+ * landed-locally row is complete, and the session-end push is its retry);
61
63
  * - `precondition`/`residue-conflict`/`conservation` ⇒ `{ ok: false, code: 1 }`
62
- * (D7/D9 unsafe-state family — the row is not published and the tree may be
63
- * left unsafe for a later whole-tree sweep);
64
+ * (the D7/D9 unsafe-state family, where the row is not published and the
65
+ * tree may be left unsafe for a later whole-tree sweep);
64
66
  * - a secret-gate refusal ⇒ `{ ok: false, code: 1 }` ({@link refusalEnvelope});
65
- * - an {@link AncestryRefusal} is rethrown so `pushRowOrRefuse` maps it to the
66
- * not-published non-zero envelope;
67
+ * - this function rethrows an {@link AncestryRefusal} so `pushRowOrRefuse`
68
+ * maps it to the not-published non-zero envelope;
67
69
  * - any other thrown error is a network/credential failure that degrades to
68
70
  * "saved locally" (`{ ok: true }`).
69
71
  *
70
72
  * The `reapply` closure re-derives this row against the fresh tip if the
71
- * landing contends, so a parallel writer's row is never erased.
73
+ * landing contends, so this command never erases a parallel writer's row.
72
74
  *
73
75
  * @param {object} wikiSync - The WikiSync collaborator (may be absent in tests).
74
76
  * @param {object} runtime - The runtime bag (for stdout/stderr).
@@ -82,37 +84,38 @@ async function pushWiki(wikiSync, runtime, message, reapply) {
82
84
  let result;
83
85
  try {
84
86
  await wikiSync.inheritIdentity();
85
- // claim/release contract is a 1-line MEMORY.md change; the pathspec keeps
86
- // foreign uncommitted files from parallel writers out of the commit. The
87
- // `reapply` closure re-derives this row against the fresh tip if the landing
88
- // contends (the singleton merge discipline), so a parallel writer's row is never erased.
87
+ // The claim/release contract is a 1-line MEMORY.md change. The pathspec
88
+ // keeps foreign uncommitted files from parallel writers out of the commit.
89
+ // The `reapply` closure re-derives this row against the fresh tip if the
90
+ // landing contends (the singleton merge discipline), so this command never
91
+ // erases a parallel writer's row.
89
92
  result = await wikiSync.commitAndPush(message, ["MEMORY.md"], { reapply });
90
93
  } catch (err) {
91
- // An ancestry-guard refusal pierces the saved-locally degradation: rethrow
92
- // so pushRowOrRefuse maps it to the not-published non-zero envelope.
94
+ // An ancestry-guard refusal pierces the saved-locally degradation. Rethrow
95
+ // it so pushRowOrRefuse maps it to the not-published non-zero envelope.
93
96
  if (err instanceof AncestryRefusal) throw err;
94
97
  if (err instanceof WikiPushFailure) {
95
- // D7/D9 unsafe-state family: the row is not published and the tree may be
96
- // left unsafe for a later sweep — fail the command closed (non-zero).
98
+ // D7/D9 unsafe-state family. The row is not published, and the tree may
99
+ // be left unsafe for a later sweep. Fail the command closed (non-zero).
97
100
  if (UNSAFE_STATE_REASONS.has(err.reason)) {
98
101
  runtime.proc.stderr.write(`${err.message}\n`);
99
102
  return { ok: false, code: 1 };
100
103
  }
101
- // rejected / transport: the local row landed; warn and keep zero exit.
104
+ // rejected / transport: the local row landed. Warn and keep zero exit.
102
105
  runtime.proc.stderr.write(
103
- `saved locally — not yet visible to parallel sessions (${err.reason}): ${err.message}\n`,
106
+ `saved locally, not yet visible to parallel sessions (${err.reason}): ${err.message}\n`,
104
107
  );
105
108
  return { ok: true };
106
109
  }
107
- // Any other failure: preserve fire-and-forget "saved locally" — the change
108
- // is on disk and the command still succeeds.
110
+ // Any other failure: preserve fire-and-forget "saved locally". The change
111
+ // is on disk, and the command still succeeds.
109
112
  createLogger("wiki", runtime).warn(
110
113
  "claim",
111
114
  `push failed (saved locally): ${err.message}`,
112
115
  );
113
116
  return { ok: true };
114
117
  }
115
- // A secret-gate refusal fails the command closed; a grounded-landed or a
118
+ // A secret-gate refusal fails the command closed. A grounded-landed or a
116
119
  // re-applied push reports success.
117
120
  const refusal = refusalEnvelope(runtime, result);
118
121
  if (refusal) return refusal;
@@ -123,16 +126,17 @@ async function pushWiki(wikiSync, runtime, message, reapply) {
123
126
  }
124
127
 
125
128
  /**
126
- * Push a written claim/release row, mapping an ancestry-guard refusal to the
127
- * not-published non-zero envelope and any other outcome to `pushWiki`'s
128
- * envelope. The row is already written to MEMORY.md; on refusal it stays as an
129
+ * Push a written claim/release row. Map an ancestry-guard refusal to the
130
+ * not-published non-zero envelope. Map any other outcome to `pushWiki`'s
131
+ * envelope. The row is already in MEMORY.md. On a refusal it stays as an
129
132
  * uncommitted working-tree change. The `reapply` closure re-derives the same
130
133
  * row against the fresh tip when the landing contends.
131
134
  */
132
135
  async function pushRowOrRefuse(wikiSync, runtime, message, reapply) {
133
136
  try {
134
137
  // Propagate pushWiki's envelope so a secret-gate or unsafe-state refusal
135
- // ({ ok: false }) fails the command closed; a clean push returns { ok: true }.
138
+ // ({ ok: false }) fails the command closed. A clean push returns
139
+ // { ok: true }.
136
140
  return await pushWiki(wikiSync, runtime, message, reapply);
137
141
  } catch (err) {
138
142
  if (err instanceof AncestryRefusal) {
@@ -143,14 +147,14 @@ async function pushRowOrRefuse(wikiSync, runtime, message, reapply) {
143
147
  }
144
148
  }
145
149
 
146
- /** Insert a row into MEMORY.md `## Active Claims`. Refuses if (agent, target) already present. */
150
+ /** Insert a row into MEMORY.md `## Active Claims`. It refuses if (agent, target) is already present. */
147
151
  export async function runClaimCommand(ctx) {
148
152
  const { runtime, wikiSync } = ctx.deps;
149
153
  const options = ctx.options;
150
154
  const resolved = requireAgentFlag(options, {
151
155
  command: "claim",
152
156
  example:
153
- "fit-wiki claim --agent staff-engineer --target spec-NNNN --branch claude/...",
157
+ "gemba-wiki claim --agent staff-engineer --target spec-NNNN --branch claude/...",
154
158
  });
155
159
  if (!resolved.ok) return resolved;
156
160
  const agent = resolved.agent;
@@ -162,8 +166,9 @@ export async function runClaimCommand(ctx) {
162
166
  };
163
167
  }
164
168
  const today = options.today || currentDayIso(runtime);
165
- // Default expiry is claim+1 day: a claim is a short-lived "actively shipping
166
- // this now" assertion, not a long lease. A run that outlives one day re-claims.
169
+ // Default expiry is claim+1 day. A claim is a short-lived "actively shipping
170
+ // this now" assertion. It is not a long lease. A run that outlives one day
171
+ // re-claims.
167
172
  const expires = options["expires-at"] || addDays(today, 1);
168
173
  const memPath = memoryPath(runtime, options);
169
174
  const text = readMemory(runtime, memPath);
@@ -220,8 +225,8 @@ export async function runReleaseCommand(ctx) {
220
225
  }
221
226
  runtime.fsSync.writeFileSync(memPath, current);
222
227
  runtime.proc.stdout.write(`released ${count} expired claim(s)\n`);
223
- // Re-derive expiry against the fresh tip so a renewal landed since the stale
224
- // read survives; only still-expired rows are removed.
228
+ // Re-derive expiry against the fresh tip so a renewal landed since the
229
+ // stale read survives. Remove only the rows that are still expired.
225
230
  const reapply = (fresh) => {
226
231
  const freshExpired = filterExpired(parseClaims(fresh), today).expired;
227
232
  let next = fresh;
@@ -245,7 +250,7 @@ export async function runReleaseCommand(ctx) {
245
250
 
246
251
  const resolved = requireAgentFlag(options, {
247
252
  command: "release",
248
- example: "fit-wiki release --agent staff-engineer --target spec-NNNN",
253
+ example: "gemba-wiki release --agent staff-engineer --target spec-NNNN",
249
254
  });
250
255
  if (!resolved.ok) return resolved;
251
256
  const agent = resolved.agent;
@@ -265,8 +270,9 @@ export async function runReleaseCommand(ctx) {
265
270
  return { ok: true };
266
271
  }
267
272
  runtime.proc.stdout.write(`released ${options.target}\n`);
268
- // Re-apply the same removal against the fresh tip if the landing contends;
269
- // re-removing an absent row is a no-op, so a re-release never resurrects it.
273
+ // Re-apply the same removal against the fresh tip if the landing contends.
274
+ // A second removal of an absent row is a no-op, so a re-release never
275
+ // resurrects it.
270
276
  const reapply = (fresh) => {
271
277
  const r = removeClaim(fresh, { agent, target: options.target });
272
278
  return r.removed ? r.text : null;