@forwardimpact/libwiki 0.2.34 → 0.3.0
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/README.md +23 -23
- package/package.json +6 -8
- package/src/audit/rules.js +10 -10
- package/src/audit/scopes.js +1 -1
- package/src/cli-definition.js +17 -17
- package/src/commands/boot.js +1 -1
- package/src/commands/claim.js +2 -2
- package/src/commands/curate.js +2 -2
- package/src/commands/fix.js +6 -6
- package/src/commands/inbox.js +1 -1
- package/src/commands/init.js +1 -1
- package/src/commands/ledger.js +1 -1
- package/src/commands/log.js +1 -1
- package/src/commands/memo.js +1 -1
- package/src/commands/product-mix.js +3 -3
- package/src/commands/rotate.js +1 -1
- package/src/constants.js +2 -2
- package/src/ledger/projection.js +1 -1
- package/src/storyboard-skeleton.js +1 -1
- package/src/wiki-sync.js +20 -20
- package/bin/fit-wiki.js +0 -94
package/README.md
CHANGED
|
@@ -14,15 +14,15 @@ storyboards. `libwiki` keeps that wiki coherent across sessions — agents boot
|
|
|
14
14
|
from it, write decisions back, send memos to each other, and audit the
|
|
15
15
|
result against a declarative rule set.
|
|
16
16
|
|
|
17
|
-
The primary interface is the `
|
|
17
|
+
The primary interface is the `gemba-wiki` CLI. The library also exposes a few
|
|
18
18
|
helpers for programmatic use.
|
|
19
19
|
|
|
20
20
|
## Getting started
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
npx
|
|
24
|
-
npx
|
|
25
|
-
npx
|
|
23
|
+
npx gemba-wiki init
|
|
24
|
+
npx gemba-wiki boot --agent staff-engineer
|
|
25
|
+
npx gemba-wiki audit
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## CLI
|
|
@@ -36,7 +36,7 @@ sweep that runs without `--agent`.
|
|
|
36
36
|
### `boot` — start a session
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
|
-
npx
|
|
39
|
+
npx gemba-wiki boot --agent staff-engineer [--format json|markdown]
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Print the on-boot digest for the agent: own priorities, cross-cutting
|
|
@@ -45,9 +45,9 @@ priorities, active claims, storyboard items, inbox count.
|
|
|
45
45
|
### `log` — record decisions, notes, done
|
|
46
46
|
|
|
47
47
|
```sh
|
|
48
|
-
npx
|
|
49
|
-
npx
|
|
50
|
-
npx
|
|
48
|
+
npx gemba-wiki log decision --agent X --surveyed "..." --chosen "..." --rationale "..."
|
|
49
|
+
npx gemba-wiki log note --agent X --field "PR Status" --body "merged"
|
|
50
|
+
npx gemba-wiki log done --agent X
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
Appends to `wiki/<agent>-YYYY-WVV.md`. Auto-rotates to `*-partN.md` when the
|
|
@@ -56,9 +56,9 @@ line budget would be exceeded.
|
|
|
56
56
|
### `claim` / `release` — coordinate work
|
|
57
57
|
|
|
58
58
|
```sh
|
|
59
|
-
npx
|
|
60
|
-
npx
|
|
61
|
-
npx
|
|
59
|
+
npx gemba-wiki claim --agent X --target spec-NNNN --branch claude/spec-NNNN
|
|
60
|
+
npx gemba-wiki release --agent X --target spec-NNNN
|
|
61
|
+
npx gemba-wiki release --expired
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
Maintains the `## Active Claims` table in `MEMORY.md`. Duplicates refused;
|
|
@@ -68,10 +68,10 @@ claim is a short-lived "shipping this now" assertion, not a lease.
|
|
|
68
68
|
### `inbox` — triage memos
|
|
69
69
|
|
|
70
70
|
```sh
|
|
71
|
-
npx
|
|
72
|
-
npx
|
|
73
|
-
npx
|
|
74
|
-
npx
|
|
71
|
+
npx gemba-wiki inbox list --agent X
|
|
72
|
+
npx gemba-wiki inbox ack --agent X --index 0
|
|
73
|
+
npx gemba-wiki inbox promote --agent X --index 0 [--owner X]
|
|
74
|
+
npx gemba-wiki inbox drop --agent X --index 0
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
Reads bullets under the `<!-- memo:inbox -->` marker in the agent's summary.
|
|
@@ -80,8 +80,8 @@ Reads bullets under the `<!-- memo:inbox -->` marker in the agent's summary.
|
|
|
80
80
|
### `memo` — cross-team coordination
|
|
81
81
|
|
|
82
82
|
```sh
|
|
83
|
-
npx
|
|
84
|
-
npx
|
|
83
|
+
npx gemba-wiki memo --from X --to Y --message "audit d642ff0c"
|
|
84
|
+
npx gemba-wiki memo --from X --to all --message "new XmR baseline"
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
Inserts a bullet `- YYYY-MM-DD from **X**: ...` after the recipient's
|
|
@@ -90,7 +90,7 @@ Inserts a bullet `- YYYY-MM-DD from **X**: ...` after the recipient's
|
|
|
90
90
|
### `audit` — verify wiki state
|
|
91
91
|
|
|
92
92
|
```sh
|
|
93
|
-
npx
|
|
93
|
+
npx gemba-wiki audit [--format text|json]
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
Runs a declarative catalogue of rules across the wiki. Exits 0 on pass, 1
|
|
@@ -107,7 +107,7 @@ Each finding carries a stable `id` for filtering. The catalogue lives in
|
|
|
107
107
|
### `rotate` — force a part split
|
|
108
108
|
|
|
109
109
|
```sh
|
|
110
|
-
npx
|
|
110
|
+
npx gemba-wiki rotate --agent X
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
Renames the current weekly log to the next `-partN.md` and starts a fresh
|
|
@@ -116,7 +116,7 @@ main file.
|
|
|
116
116
|
### `refresh` — re-render storyboard blocks
|
|
117
117
|
|
|
118
118
|
```sh
|
|
119
|
-
npx
|
|
119
|
+
npx gemba-wiki refresh [storyboard-path]
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
Re-renders `<!-- xmr:metric:csv-path -->` and `<!-- obstacles:open[:Nd] -->`
|
|
@@ -128,9 +128,9 @@ deterministic refresh.
|
|
|
128
128
|
### `init` / `push` / `pull` — wiki working tree
|
|
129
129
|
|
|
130
130
|
```sh
|
|
131
|
-
npx
|
|
132
|
-
npx
|
|
133
|
-
npx
|
|
131
|
+
npx gemba-wiki init [--wiki-root wiki] [--skills-dir .claude/skills]
|
|
132
|
+
npx gemba-wiki push
|
|
133
|
+
npx gemba-wiki pull
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
`init` clones the wiki repo if missing, scaffolds Active Claims in
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/libwiki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Wiki lifecycle for agent teams — persistent memory, declarative integrity audits, and a collision ledger so coordination survives across sessions and parallel work.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wiki",
|
|
@@ -31,14 +31,12 @@
|
|
|
31
31
|
"main": "./src/index.js",
|
|
32
32
|
"exports": {
|
|
33
33
|
".": "./src/index.js",
|
|
34
|
-
"./
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"fit-wiki": "./bin/fit-wiki.js"
|
|
34
|
+
"./wiki-sync.js": "./src/wiki-sync.js",
|
|
35
|
+
"./util/wiki-dir.js": "./src/util/wiki-dir.js",
|
|
36
|
+
"./cli-definition.js": "./src/cli-definition.js"
|
|
38
37
|
},
|
|
39
38
|
"files": [
|
|
40
39
|
"src/**/*.js",
|
|
41
|
-
"bin/**/*.js",
|
|
42
40
|
"README.md"
|
|
43
41
|
],
|
|
44
42
|
"scripts": {
|
|
@@ -47,11 +45,11 @@
|
|
|
47
45
|
"dependencies": {
|
|
48
46
|
"@forwardimpact/libcli": "^0.1.0",
|
|
49
47
|
"@forwardimpact/libconfig": "^0.1.77",
|
|
50
|
-
"@forwardimpact/libharness": "^
|
|
48
|
+
"@forwardimpact/libharness": "^3.0.0",
|
|
51
49
|
"@forwardimpact/libpreflight": "^0.1.0",
|
|
52
50
|
"@forwardimpact/libtelemetry": "^0.1.47",
|
|
53
51
|
"@forwardimpact/libutil": "^0.1.0",
|
|
54
|
-
"@forwardimpact/libxmr": "^
|
|
52
|
+
"@forwardimpact/libxmr": "^3.0.0"
|
|
55
53
|
},
|
|
56
54
|
"devDependencies": {
|
|
57
55
|
"@forwardimpact/libmock": "^0.1.0"
|
package/src/audit/rules.js
CHANGED
|
@@ -94,7 +94,7 @@ export const RULES = [
|
|
|
94
94
|
when: (s) => s.h2s.includes("Message Inbox"),
|
|
95
95
|
check: containsLine(MEMO_INBOX_MARKER),
|
|
96
96
|
message: () => `Missing ${MEMO_INBOX_MARKER} marker`,
|
|
97
|
-
hint: "add the marker directly below the '## Message Inbox' heading so `
|
|
97
|
+
hint: "add the marker directly below the '## Message Inbox' heading so `gemba-wiki memo` can find it",
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
id: "summary.open-blockers-last",
|
|
@@ -148,7 +148,7 @@ export const RULES = [
|
|
|
148
148
|
check: lineBudget(WEEKLY_LOG_LINE_BUDGET),
|
|
149
149
|
message: (_s, r) => `${r.value} lines (limit ${WEEKLY_LOG_LINE_BUDGET})`,
|
|
150
150
|
hint: (s) =>
|
|
151
|
-
`run \`bunx
|
|
151
|
+
`run \`bunx gemba-wiki rotate --agent ${s.agentPrefix}\` to seal this file as a sealed part and start a fresh weekly log`,
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
154
|
id: "weekly-log.word-budget",
|
|
@@ -158,7 +158,7 @@ export const RULES = [
|
|
|
158
158
|
check: wordBudget(WEEKLY_LOG_WORD_BUDGET),
|
|
159
159
|
message: (_s, r) => `${r.value} words (limit ${WEEKLY_LOG_WORD_BUDGET})`,
|
|
160
160
|
hint: (s) =>
|
|
161
|
-
`run \`bunx
|
|
161
|
+
`run \`bunx gemba-wiki rotate --agent ${s.agentPrefix}\` to seal this file as a sealed part and start a fresh weekly log`,
|
|
162
162
|
},
|
|
163
163
|
{
|
|
164
164
|
id: "weekly-log.h1-agent-matches-filename",
|
|
@@ -176,7 +176,7 @@ export const RULES = [
|
|
|
176
176
|
check: headingGrammarDrift,
|
|
177
177
|
message: (_s, r) =>
|
|
178
178
|
`Entry heading '${r.observed}' does not match the dated grammar`,
|
|
179
|
-
hint: "weekly-log entry headings must be '## YYYY-MM-DD'; open entries with `
|
|
179
|
+
hint: "weekly-log entry headings must be '## YYYY-MM-DD'; open entries with `gemba-wiki log decision/note`, which emit a conforming heading the rotation seam-finder can split",
|
|
180
180
|
},
|
|
181
181
|
{
|
|
182
182
|
id: "decision-block.heading-within-5",
|
|
@@ -191,7 +191,7 @@ export const RULES = [
|
|
|
191
191
|
r.nearMiss
|
|
192
192
|
? `Entry opens with '${r.nearMiss}'; the heading must be exactly '${DECISION_HEADING}' — move the suffix into the body`
|
|
193
193
|
: `Entry lacks a line that is exactly '${DECISION_HEADING}'`,
|
|
194
|
-
hint: `open each '## YYYY-MM-DD' entry with \`
|
|
194
|
+
hint: `open each '## YYYY-MM-DD' entry with \`gemba-wiki log decision\`, which emits a line containing exactly '${DECISION_HEADING}' (no suffix — the check is an exact match); put the one-line summary in the body below it, drawn from the entry's own narrative — do not invent rationale the entry does not support`,
|
|
195
195
|
},
|
|
196
196
|
|
|
197
197
|
// -- Weekly logs (sealed parts) --
|
|
@@ -211,7 +211,7 @@ export const RULES = [
|
|
|
211
211
|
check: headingGrammarDrift,
|
|
212
212
|
message: (_s, r) =>
|
|
213
213
|
`Entry heading '${r.observed}' does not match the dated grammar`,
|
|
214
|
-
hint: "weekly-log entry headings must be '## YYYY-MM-DD'; open entries with `
|
|
214
|
+
hint: "weekly-log entry headings must be '## YYYY-MM-DD'; open entries with `gemba-wiki log decision/note`, which emit a conforming heading the rotation seam-finder can split",
|
|
215
215
|
},
|
|
216
216
|
{
|
|
217
217
|
id: "weekly-log-part.line-budget",
|
|
@@ -220,7 +220,7 @@ export const RULES = [
|
|
|
220
220
|
remediation: "rotate",
|
|
221
221
|
check: lineBudget(WEEKLY_LOG_LINE_BUDGET),
|
|
222
222
|
message: (_s, r) => `${r.value} lines (limit ${WEEKLY_LOG_LINE_BUDGET})`,
|
|
223
|
-
hint: "`bunx
|
|
223
|
+
hint: "`bunx gemba-wiki fix` re-bisects an over-budget part at its day-section seams and, for a lone over-cap day, at its '### ' block seams; only a single '### ' block that alone exceeds the budget remains for a human to shorten",
|
|
224
224
|
},
|
|
225
225
|
{
|
|
226
226
|
id: "weekly-log-part.word-budget",
|
|
@@ -229,7 +229,7 @@ export const RULES = [
|
|
|
229
229
|
remediation: "rotate",
|
|
230
230
|
check: wordBudget(WEEKLY_LOG_WORD_BUDGET),
|
|
231
231
|
message: (_s, r) => `${r.value} words (limit ${WEEKLY_LOG_WORD_BUDGET})`,
|
|
232
|
-
hint: "`bunx
|
|
232
|
+
hint: "`bunx gemba-wiki fix` re-bisects an over-budget part at its day-section seams and, for a lone over-cap day, at its '### ' block seams; only a single '### ' block that alone exceeds the budget remains for a human to shorten",
|
|
233
233
|
},
|
|
234
234
|
{
|
|
235
235
|
id: "weekly-log-part.h1-agent-matches-filename",
|
|
@@ -273,7 +273,7 @@ export const RULES = [
|
|
|
273
273
|
severity: "fail",
|
|
274
274
|
check: exists,
|
|
275
275
|
message: () => "MEMORY.md not found",
|
|
276
|
-
hint: "run `bunx
|
|
276
|
+
hint: "run `bunx gemba-wiki init` to scaffold the canonical sections",
|
|
277
277
|
},
|
|
278
278
|
{
|
|
279
279
|
id: "memory.line-budget",
|
|
@@ -371,7 +371,7 @@ export const RULES = [
|
|
|
371
371
|
severity: "warn",
|
|
372
372
|
check: expired,
|
|
373
373
|
message: (s) => `${s.agent}/${s.target} expired ${s.expires_at}`,
|
|
374
|
-
hint: "run `bunx
|
|
374
|
+
hint: "run `bunx gemba-wiki refresh` (or `release --expired`) to clear expired claims",
|
|
375
375
|
},
|
|
376
376
|
|
|
377
377
|
// -- Storyboards --
|
package/src/audit/scopes.js
CHANGED
package/src/cli-definition.js
CHANGED
|
@@ -14,7 +14,7 @@ import { runFixCommand } from "./commands/fix.js";
|
|
|
14
14
|
import { runLedgerCommand } from "./commands/ledger.js";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Build the `
|
|
17
|
+
* Build the `gemba-wiki` libcli definition. Agent identity is never resolved from
|
|
18
18
|
* the environment: agent-scoped subcommands require an explicit `--agent`
|
|
19
19
|
* (`--from` for `memo`) and fail closed without it, so this module carries no
|
|
20
20
|
* ambient agent identity. The version is resolved by libcli's `createCli` from
|
|
@@ -47,7 +47,7 @@ export function createDefinition() {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
return {
|
|
50
|
-
name: "
|
|
50
|
+
name: "gemba-wiki",
|
|
51
51
|
description: "Wiki lifecycle management for the Kata agent system",
|
|
52
52
|
commands: [
|
|
53
53
|
{
|
|
@@ -348,21 +348,21 @@ export function createDefinition() {
|
|
|
348
348
|
},
|
|
349
349
|
},
|
|
350
350
|
examples: [
|
|
351
|
-
"
|
|
352
|
-
'
|
|
353
|
-
"
|
|
354
|
-
"
|
|
355
|
-
"
|
|
356
|
-
"
|
|
357
|
-
"
|
|
358
|
-
"
|
|
359
|
-
"
|
|
360
|
-
'
|
|
361
|
-
"
|
|
362
|
-
"
|
|
363
|
-
"
|
|
364
|
-
"
|
|
365
|
-
"
|
|
351
|
+
"gemba-wiki boot --agent staff-engineer",
|
|
352
|
+
'gemba-wiki log decision --agent staff-engineer --surveyed "..." --chosen "..." --rationale "..."',
|
|
353
|
+
"gemba-wiki claim --agent staff-engineer --target spec-NNNN --branch claude/...",
|
|
354
|
+
"gemba-wiki release --agent staff-engineer --target spec-NNNN",
|
|
355
|
+
"gemba-wiki inbox list --agent staff-engineer",
|
|
356
|
+
"gemba-wiki rotate --agent staff-engineer",
|
|
357
|
+
"gemba-wiki audit",
|
|
358
|
+
"gemba-wiki curate",
|
|
359
|
+
"gemba-wiki fix",
|
|
360
|
+
'gemba-wiki memo --from staff-engineer --to security-engineer --message "audit d642ff0c"',
|
|
361
|
+
"gemba-wiki refresh",
|
|
362
|
+
"gemba-wiki product-mix",
|
|
363
|
+
"gemba-wiki init",
|
|
364
|
+
"gemba-wiki push",
|
|
365
|
+
"gemba-wiki pull",
|
|
366
366
|
],
|
|
367
367
|
documentation: [
|
|
368
368
|
{
|
package/src/commands/boot.js
CHANGED
|
@@ -46,7 +46,7 @@ export function runBootCommand(ctx) {
|
|
|
46
46
|
const options = ctx.options;
|
|
47
47
|
const resolved = requireAgentFlag(options, {
|
|
48
48
|
command: "boot",
|
|
49
|
-
example: "
|
|
49
|
+
example: "gemba-wiki boot --agent staff-engineer",
|
|
50
50
|
});
|
|
51
51
|
if (!resolved.ok) return resolved;
|
|
52
52
|
const agent = resolved.agent;
|
package/src/commands/claim.js
CHANGED
|
@@ -150,7 +150,7 @@ export async function runClaimCommand(ctx) {
|
|
|
150
150
|
const resolved = requireAgentFlag(options, {
|
|
151
151
|
command: "claim",
|
|
152
152
|
example:
|
|
153
|
-
"
|
|
153
|
+
"gemba-wiki claim --agent staff-engineer --target spec-NNNN --branch claude/...",
|
|
154
154
|
});
|
|
155
155
|
if (!resolved.ok) return resolved;
|
|
156
156
|
const agent = resolved.agent;
|
|
@@ -245,7 +245,7 @@ export async function runReleaseCommand(ctx) {
|
|
|
245
245
|
|
|
246
246
|
const resolved = requireAgentFlag(options, {
|
|
247
247
|
command: "release",
|
|
248
|
-
example: "
|
|
248
|
+
example: "gemba-wiki release --agent staff-engineer --target spec-NNNN",
|
|
249
249
|
});
|
|
250
250
|
if (!resolved.ok) return resolved;
|
|
251
251
|
const agent = resolved.agent;
|
package/src/commands/curate.js
CHANGED
|
@@ -19,7 +19,7 @@ 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 `
|
|
22
|
+
// that fit plus a count; the full list stays reproducible via `gemba-wiki audit`.
|
|
23
23
|
const MAX_BODY = 65000;
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -39,7 +39,7 @@ function buildBody(findingsJson, { shown, total } = {}) {
|
|
|
39
39
|
];
|
|
40
40
|
if (total != null && shown != null && shown < total) {
|
|
41
41
|
lines.push(
|
|
42
|
-
`Showing ${shown} of ${total} findings — the body was truncated to fit GitHub's comment limit. Run \`
|
|
42
|
+
`Showing ${shown} of ${total} findings — the body was truncated to fit GitHub's comment limit. Run \`gemba-wiki audit\` for the full list.`,
|
|
43
43
|
"",
|
|
44
44
|
);
|
|
45
45
|
}
|
package/src/commands/fix.js
CHANGED
|
@@ -122,7 +122,7 @@ function sealMainLog(agent, { wikiRoot, today, projectRoot, fs, out, err }) {
|
|
|
122
122
|
);
|
|
123
123
|
}
|
|
124
124
|
} catch (e) {
|
|
125
|
-
err(`
|
|
125
|
+
err(`gemba-wiki fix: rotate failed for ${agent}: ${e.message}\n`);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -164,7 +164,7 @@ function resealPart(partPath, { fs, projectRoot, out, err }) {
|
|
|
164
164
|
}
|
|
165
165
|
} catch (e) {
|
|
166
166
|
err(
|
|
167
|
-
`
|
|
167
|
+
`gemba-wiki fix: rebisect failed for ` +
|
|
168
168
|
`${path.relative(projectRoot, partPath)}: ${e.message}\n`,
|
|
169
169
|
);
|
|
170
170
|
}
|
|
@@ -193,7 +193,7 @@ function rebisectOverBudgetParts(findings, deps) {
|
|
|
193
193
|
/** Report findings that need human judgment — never auto-fixed. */
|
|
194
194
|
function reportFlags(err, flagFindings, projectRoot) {
|
|
195
195
|
err(
|
|
196
|
-
`
|
|
196
|
+
`gemba-wiki fix: ${flagFindings.length} finding(s) need human judgment ` +
|
|
197
197
|
`(not auto-fixable):\n` +
|
|
198
198
|
emitFindingsText(flagFindings, { cwd: projectRoot }),
|
|
199
199
|
);
|
|
@@ -209,10 +209,10 @@ function reportFlags(err, flagFindings, projectRoot) {
|
|
|
209
209
|
function isFatalError(result, round, err) {
|
|
210
210
|
if (!result.error) return false;
|
|
211
211
|
if (!result.sessionId) {
|
|
212
|
-
err(`
|
|
212
|
+
err(`gemba-wiki fix: agent run failed: ${result.error.message}\n`);
|
|
213
213
|
return true;
|
|
214
214
|
}
|
|
215
|
-
err(`
|
|
215
|
+
err(`gemba-wiki fix: round ${round} agent error: ${result.error.message}\n`);
|
|
216
216
|
return false;
|
|
217
217
|
}
|
|
218
218
|
|
|
@@ -265,7 +265,7 @@ async function runAgentRounds(runner, agentFindings, deps) {
|
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
err(
|
|
268
|
-
`
|
|
268
|
+
`gemba-wiki fix: ${agentFindings.length} finding(s) remain after ` +
|
|
269
269
|
`${MAX_ROUNDS} round(s):\n` +
|
|
270
270
|
emitFindingsText(agentFindings, { cwd: projectRoot }),
|
|
271
271
|
);
|
package/src/commands/inbox.js
CHANGED
|
@@ -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: "
|
|
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;
|
package/src/commands/init.js
CHANGED
|
@@ -32,7 +32,7 @@ function scaffoldActiveClaims(runtime, memoryPath) {
|
|
|
32
32
|
ACTIVE_CLAIMS_HEADING,
|
|
33
33
|
"",
|
|
34
34
|
"In-flight work claimed by an agent. Row present = active; row absent = settled.",
|
|
35
|
-
"Writers: `
|
|
35
|
+
"Writers: `gemba-wiki claim`, `gemba-wiki release`. Reader: `gemba-wiki boot`.",
|
|
36
36
|
"",
|
|
37
37
|
ACTIVE_CLAIMS_TABLE_HEADER,
|
|
38
38
|
ACTIVE_CLAIMS_TABLE_SEPARATOR,
|
package/src/commands/ledger.js
CHANGED
|
@@ -181,7 +181,7 @@ async function verify(env, options) {
|
|
|
181
181
|
const SUBS = { allocate, rebuild, verify };
|
|
182
182
|
|
|
183
183
|
/**
|
|
184
|
-
* `
|
|
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
186
|
* comment to the obstacle issue with no projection write at allocation time;
|
|
187
187
|
* rebuild and verify project the anchor record onto the ledger page and MEMORY
|
package/src/commands/log.js
CHANGED
|
@@ -19,7 +19,7 @@ function commonContext(runtime, options) {
|
|
|
19
19
|
const resolved = requireAgentFlag(options, {
|
|
20
20
|
command: "log",
|
|
21
21
|
example:
|
|
22
|
-
'
|
|
22
|
+
'gemba-wiki log decision --agent staff-engineer --chosen "..." --rationale "..."',
|
|
23
23
|
});
|
|
24
24
|
if (!resolved.ok) return { error: resolved };
|
|
25
25
|
const wikiRoot = resolveWikiRoot(runtime, options);
|
package/src/commands/memo.js
CHANGED
|
@@ -72,7 +72,7 @@ export function runMemoCommand(ctx) {
|
|
|
72
72
|
command: "memo",
|
|
73
73
|
flag: "--from",
|
|
74
74
|
example:
|
|
75
|
-
'
|
|
75
|
+
'gemba-wiki memo --from staff-engineer --to security-engineer --message "..."',
|
|
76
76
|
});
|
|
77
77
|
if (!resolved.ok) return resolved;
|
|
78
78
|
const sender = resolved.agent;
|
|
@@ -76,7 +76,7 @@ function countByLabel(prs) {
|
|
|
76
76
|
* Emit the product-vs-internal mix of merged PRs as a `product_share` metric
|
|
77
77
|
* row. Counts PRs merged in `[since, until]` by their `product` / `internal`
|
|
78
78
|
* label and appends `product_share = round(product / (product + internal) *
|
|
79
|
-
* 100)` to `wiki/metrics/product-mix/<YYYY>.csv` via the `
|
|
79
|
+
* 100)` to `wiki/metrics/product-mix/<YYYY>.csv` via the `gemba-xmr record` write
|
|
80
80
|
* path. Deterministic — re-running over the same merged PRs yields the same
|
|
81
81
|
* value. A window with no labeled merged PRs emits no row (avoids a 0/0 ratio).
|
|
82
82
|
*/
|
|
@@ -117,7 +117,7 @@ export async function runProductMixCommand(ctx) {
|
|
|
117
117
|
|
|
118
118
|
const share = Math.round((product / total) * 100);
|
|
119
119
|
const recordArgs = [
|
|
120
|
-
"
|
|
120
|
+
"gemba-xmr",
|
|
121
121
|
"record",
|
|
122
122
|
"--skill",
|
|
123
123
|
"product-mix",
|
|
@@ -142,7 +142,7 @@ export async function runProductMixCommand(ctx) {
|
|
|
142
142
|
|
|
143
143
|
const recordResult = await runtime.subprocess.run("npx", recordArgs, { cwd });
|
|
144
144
|
if (recordResult.exitCode !== 0) {
|
|
145
|
-
logger.warn("product-mix", "
|
|
145
|
+
logger.warn("product-mix", "gemba-xmr record failed");
|
|
146
146
|
}
|
|
147
147
|
return { ok: true };
|
|
148
148
|
}
|
package/src/commands/rotate.js
CHANGED
|
@@ -15,7 +15,7 @@ export function runRotateCommand(ctx) {
|
|
|
15
15
|
const options = ctx.options;
|
|
16
16
|
const resolved = requireAgentFlag(options, {
|
|
17
17
|
command: "rotate",
|
|
18
|
-
example: "
|
|
18
|
+
example: "gemba-wiki rotate --agent staff-engineer",
|
|
19
19
|
});
|
|
20
20
|
if (!resolved.ok) return resolved;
|
|
21
21
|
const agent = resolved.agent;
|
package/src/constants.js
CHANGED
|
@@ -94,7 +94,7 @@ export const CARRY_SURFACE_H1_RE = /^# (.+) — Carries$/;
|
|
|
94
94
|
export const CARRY_CLEARANCE_MARKER_RE = /\*\*Carry-clearance:\*\*/;
|
|
95
95
|
|
|
96
96
|
// Storyboard marker syntax. An open or close marker tolerates optional trailing
|
|
97
|
-
// text after the tag (typically an inline "Do not edit. Generated from
|
|
97
|
+
// text after the tag (typically an inline "Do not edit. Generated from gemba-wiki
|
|
98
98
|
// refresh." notice). One home so the marker scanner (marker-scanner.js) and the
|
|
99
99
|
// audit's balance check (audit/rules.js) cannot drift on the syntax.
|
|
100
100
|
// Capture groups: 1 metric, 2 csvPath, 3 optional prior-read anchor date. The
|
|
@@ -110,7 +110,7 @@ export const ISSUE_CLOSE_RE =
|
|
|
110
110
|
|
|
111
111
|
// Materialized per-agent experiments surface. A distinct marker
|
|
112
112
|
// kind from `experiments:open` — it carries attributed, sanitized items plus a
|
|
113
|
-
// last-successful-sync stamp, and is read offline by `
|
|
113
|
+
// last-successful-sync stamp, and is read offline by `gemba-wiki boot`. One home
|
|
114
114
|
// so the scanner (marker-scanner.js), the refresh renderer (commands/refresh.js),
|
|
115
115
|
// the boot parser (boot.js), and the audit balance check (audit/rules.js) cannot
|
|
116
116
|
// drift on the syntax.
|
package/src/ledger/projection.js
CHANGED
|
@@ -72,7 +72,7 @@ export function renderLedgerPage(
|
|
|
72
72
|
const lines = [
|
|
73
73
|
"# Parallel-Collision Ledger",
|
|
74
74
|
"",
|
|
75
|
-
"Derived projection of the allocation-anchor record. Rebuilt by `
|
|
75
|
+
"Derived projection of the allocation-anchor record. Rebuilt by `gemba-wiki ledger rebuild`; do not hand-edit identifiers here — allocate at an anchor.",
|
|
76
76
|
"",
|
|
77
77
|
...renderKindSections(fold),
|
|
78
78
|
...renderConflicts(fold, labelMode),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Storyboard skeleton — the minimal, valid storyboard file `
|
|
2
|
+
* Storyboard skeleton — the minimal, valid storyboard file `gemba-wiki refresh`
|
|
3
3
|
* writes when the current-month board does not yet exist. It carries only the
|
|
4
4
|
* structural surface libwiki owns: the five Toyota Kata sections and the
|
|
5
5
|
* generic `obstacles`/`experiments` issue-list marker blocks that refresh
|
package/src/wiki-sync.js
CHANGED
|
@@ -319,7 +319,7 @@ export class WikiSync {
|
|
|
319
319
|
*
|
|
320
320
|
* The commit is always pathspec-scoped: with `paths` the scope is the
|
|
321
321
|
* caller's declared write-set, and without `paths` it is the session's own
|
|
322
|
-
* dirty set (`#dirtyPaths`, the `
|
|
322
|
+
* dirty set (`#dirtyPaths`, the `gemba-wiki push` contract under per-session
|
|
323
323
|
* checkout isolation). The whole-tree `add -A` sweep is gone, so foreign
|
|
324
324
|
* content on undeclared paths is never staged and the stale fast-forward
|
|
325
325
|
* eraser it carried no longer exists; the rebase runs with --autostash
|
|
@@ -465,7 +465,7 @@ export class WikiSync {
|
|
|
465
465
|
).changed;
|
|
466
466
|
// Attribute the commit's write-set. A caller that knows
|
|
467
467
|
// its narrower write-set passes `paths` (the claim/release path, scoped to
|
|
468
|
-
// MEMORY.md). The session-close `
|
|
468
|
+
// MEMORY.md). The session-close `gemba-wiki push` passes none and the
|
|
469
469
|
// write-set is the session's own dirty set, read from the working tree —
|
|
470
470
|
// correct because the canonical mechanism runs it in a per-session isolated
|
|
471
471
|
// checkout where the dirty set holds no foreign content. The whole-tree
|
|
@@ -638,7 +638,7 @@ export class WikiSync {
|
|
|
638
638
|
const files = [...new Set(gate.refusals.map((r) => r.file))].join(", ");
|
|
639
639
|
return new WikiPushFailure(
|
|
640
640
|
PUSH_REASONS.BUDGET,
|
|
641
|
-
"
|
|
641
|
+
"gemba-wiki: refusing to push — it would introduce or deepen a budget " +
|
|
642
642
|
`breach in ${files} (${lead.ruleId}: ${lead.value} vs baseline ` +
|
|
643
643
|
`${lead.baseline}). Trim your own content or surface the carried ` +
|
|
644
644
|
"content to its owner, then re-push; your work is committed locally.",
|
|
@@ -682,7 +682,7 @@ export class WikiSync {
|
|
|
682
682
|
});
|
|
683
683
|
throw new WikiPushFailure(
|
|
684
684
|
PUSH_REASONS.RESIDUE_CONFLICT,
|
|
685
|
-
"
|
|
685
|
+
"gemba-wiki: refusing to push — a foreign writer's residue conflicted " +
|
|
686
686
|
"on the autostash pop; your stash is preserved at " +
|
|
687
687
|
`${stashSha || "refs/stash"} (git stash list). Resolve or pop it ` +
|
|
688
688
|
"from the true tip.",
|
|
@@ -736,8 +736,8 @@ export class WikiSync {
|
|
|
736
736
|
// No-intent path: fail loud rather than discard the remote side (D2).
|
|
737
737
|
throw new WikiPushFailure(
|
|
738
738
|
PUSH_REASONS.CONFLICT,
|
|
739
|
-
"
|
|
740
|
-
"Resolve or retry from the true tip (
|
|
739
|
+
"gemba-wiki: refusing to push — rebase conflict with the remote. " +
|
|
740
|
+
"Resolve or retry from the true tip (gemba-wiki pull, then push).",
|
|
741
741
|
);
|
|
742
742
|
}
|
|
743
743
|
|
|
@@ -923,7 +923,7 @@ export class WikiSync {
|
|
|
923
923
|
if (this.#rebaseInProgress()) {
|
|
924
924
|
throw new WikiPushFailure(
|
|
925
925
|
PUSH_REASONS.PRECONDITION,
|
|
926
|
-
"
|
|
926
|
+
"gemba-wiki: refusing to act — a rebase is in progress. Resolve or " +
|
|
927
927
|
"abort it before retrying; your uncommitted edit is preserved.",
|
|
928
928
|
);
|
|
929
929
|
}
|
|
@@ -1061,7 +1061,7 @@ export class WikiSync {
|
|
|
1061
1061
|
this.#reportConservation("refusal");
|
|
1062
1062
|
throw new WikiPushFailure(
|
|
1063
1063
|
PUSH_REASONS.CONSERVATION,
|
|
1064
|
-
"
|
|
1064
|
+
"gemba-wiki: refusing to push — it would drop another writer's " +
|
|
1065
1065
|
`content in ${file} that is present on the remote. Pull and ` +
|
|
1066
1066
|
"re-apply, or declare the removal if it is deliberate.",
|
|
1067
1067
|
);
|
|
@@ -1176,7 +1176,7 @@ export class WikiSync {
|
|
|
1176
1176
|
}
|
|
1177
1177
|
|
|
1178
1178
|
#sidecarPath() {
|
|
1179
|
-
return path.join(this.#wikiDir, ".git", "
|
|
1179
|
+
return path.join(this.#wikiDir, ".git", "gemba-wiki-removal-intent");
|
|
1180
1180
|
}
|
|
1181
1181
|
|
|
1182
1182
|
/** Read the clone-local removal-intent sidecar (declared deliberate removals). */
|
|
@@ -1226,7 +1226,7 @@ export class WikiSync {
|
|
|
1226
1226
|
reason: PUSH_REASONS.TRANSPORT,
|
|
1227
1227
|
error: new WikiPushFailure(
|
|
1228
1228
|
PUSH_REASONS.TRANSPORT,
|
|
1229
|
-
"
|
|
1229
|
+
"gemba-wiki: push failed at transport (network or credentials). " +
|
|
1230
1230
|
"Your work is committed locally; retry when connectivity returns.",
|
|
1231
1231
|
),
|
|
1232
1232
|
};
|
|
@@ -1240,7 +1240,7 @@ export class WikiSync {
|
|
|
1240
1240
|
reason: PUSH_REASONS.TRANSPORT,
|
|
1241
1241
|
error: new WikiPushFailure(
|
|
1242
1242
|
PUSH_REASONS.TRANSPORT,
|
|
1243
|
-
"
|
|
1243
|
+
"gemba-wiki: push did not land and the remote could not be observed " +
|
|
1244
1244
|
"(network or credentials). Your work is committed locally.",
|
|
1245
1245
|
),
|
|
1246
1246
|
};
|
|
@@ -1250,8 +1250,8 @@ export class WikiSync {
|
|
|
1250
1250
|
reason: PUSH_REASONS.REJECTED,
|
|
1251
1251
|
error: new WikiPushFailure(
|
|
1252
1252
|
PUSH_REASONS.REJECTED,
|
|
1253
|
-
"
|
|
1254
|
-
"tip (
|
|
1253
|
+
"gemba-wiki: push rejected — the remote advanced. Rerun from the true " +
|
|
1254
|
+
"tip (gemba-wiki pull, then push).",
|
|
1255
1255
|
),
|
|
1256
1256
|
};
|
|
1257
1257
|
}
|
|
@@ -1309,7 +1309,7 @@ export class WikiSync {
|
|
|
1309
1309
|
if ((await this.#git.headBranch({ cwd })) !== BRANCH) {
|
|
1310
1310
|
throw new AncestryRefusal(
|
|
1311
1311
|
"unverifiable",
|
|
1312
|
-
"
|
|
1312
|
+
"gemba-wiki: refusing to publish — HEAD is detached, so the configured " +
|
|
1313
1313
|
"branch would be pushed instead of your work. Re-clone the wiki.",
|
|
1314
1314
|
);
|
|
1315
1315
|
}
|
|
@@ -1325,7 +1325,7 @@ export class WikiSync {
|
|
|
1325
1325
|
} catch {
|
|
1326
1326
|
throw new AncestryRefusal(
|
|
1327
1327
|
"unverifiable",
|
|
1328
|
-
"
|
|
1328
|
+
"gemba-wiki: refusing to publish — could not observe the remote to " +
|
|
1329
1329
|
"verify ancestry; the local change is not published.",
|
|
1330
1330
|
);
|
|
1331
1331
|
}
|
|
@@ -1345,7 +1345,7 @@ export class WikiSync {
|
|
|
1345
1345
|
} catch {
|
|
1346
1346
|
throw new AncestryRefusal(
|
|
1347
1347
|
"unverifiable",
|
|
1348
|
-
"
|
|
1348
|
+
"gemba-wiki: refusing to publish — could not fetch the remote branch " +
|
|
1349
1349
|
"to verify ancestry; the local change is not published.",
|
|
1350
1350
|
);
|
|
1351
1351
|
}
|
|
@@ -1355,7 +1355,7 @@ export class WikiSync {
|
|
|
1355
1355
|
if (!(await this.#git.refExists("HEAD", { cwd }))) {
|
|
1356
1356
|
throw new AncestryRefusal(
|
|
1357
1357
|
"unrelated",
|
|
1358
|
-
"
|
|
1358
|
+
"gemba-wiki: refusing to publish — HEAD is unborn but the remote " +
|
|
1359
1359
|
"branch exists. Re-clone the wiki.",
|
|
1360
1360
|
);
|
|
1361
1361
|
}
|
|
@@ -1367,7 +1367,7 @@ export class WikiSync {
|
|
|
1367
1367
|
if (!this.#isShallow()) {
|
|
1368
1368
|
throw new AncestryRefusal(
|
|
1369
1369
|
"unrelated",
|
|
1370
|
-
"
|
|
1370
|
+
"gemba-wiki: refusing to publish — local history is unrelated to the " +
|
|
1371
1371
|
"remote branch. Re-clone the wiki.",
|
|
1372
1372
|
);
|
|
1373
1373
|
}
|
|
@@ -1377,14 +1377,14 @@ export class WikiSync {
|
|
|
1377
1377
|
if (deepen.exitCode !== 0) {
|
|
1378
1378
|
throw new AncestryRefusal(
|
|
1379
1379
|
"unverifiable",
|
|
1380
|
-
"
|
|
1380
|
+
"gemba-wiki: refusing to publish — could not deepen history to verify " +
|
|
1381
1381
|
"ancestry; the local change is not published.",
|
|
1382
1382
|
);
|
|
1383
1383
|
}
|
|
1384
1384
|
if (await this.#git.mergeBaseExists(REMOTE_BRANCH, "HEAD", { cwd })) return;
|
|
1385
1385
|
throw new AncestryRefusal(
|
|
1386
1386
|
"unrelated",
|
|
1387
|
-
"
|
|
1387
|
+
"gemba-wiki: refusing to publish — local history is unrelated to the " +
|
|
1388
1388
|
"remote branch (confirmed against full history). Re-clone the wiki.",
|
|
1389
1389
|
);
|
|
1390
1390
|
}
|
package/bin/fit-wiki.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import "@forwardimpact/libpreflight/node22";
|
|
4
|
-
|
|
5
|
-
import { createDefaultRuntime } from "@forwardimpact/libutil/runtime";
|
|
6
|
-
import { GitClient } from "@forwardimpact/libutil/git-client";
|
|
7
|
-
import { GhClient } from "@forwardimpact/libutil/gh-client";
|
|
8
|
-
import { createScriptConfig } from "@forwardimpact/libconfig";
|
|
9
|
-
import { createCli } from "@forwardimpact/libcli";
|
|
10
|
-
import { createLogger } from "@forwardimpact/libtelemetry";
|
|
11
|
-
import { WikiSync } from "../src/wiki-sync.js";
|
|
12
|
-
import {
|
|
13
|
-
resolveProjectRoot,
|
|
14
|
-
resolveWikiRoot,
|
|
15
|
-
wikiExists,
|
|
16
|
-
} from "../src/util/wiki-dir.js";
|
|
17
|
-
import { createDefinition } from "../src/cli-definition.js";
|
|
18
|
-
|
|
19
|
-
// Commands that mutate or sync the remote wiki need a constructed WikiSync
|
|
20
|
-
// (and its config-backed token resolver); the rest run against the local tree.
|
|
21
|
-
const NEEDS_WIKI_SYNC = new Set(["claim", "release", "push", "pull", "init"]);
|
|
22
|
-
|
|
23
|
-
// The ledger command reads and writes the allocation-anchor surface (the
|
|
24
|
-
// obstacle issue's comments) over the GitHub API, so it needs a GhClient.
|
|
25
|
-
const NEEDS_GH_CLIENT = new Set(["ledger"]);
|
|
26
|
-
|
|
27
|
-
async function main() {
|
|
28
|
-
const runtime = createDefaultRuntime();
|
|
29
|
-
const definition = createDefinition();
|
|
30
|
-
const cli = createCli(definition, {
|
|
31
|
-
runtime,
|
|
32
|
-
packageJsonUrl: new URL("../package.json", import.meta.url),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const parsed = cli.parse(runtime.proc.argv.slice(2));
|
|
36
|
-
if (!parsed) return runtime.proc.exit(0); // --help / --version already printed
|
|
37
|
-
|
|
38
|
-
const { positionals } = parsed;
|
|
39
|
-
if (positionals.length === 0) {
|
|
40
|
-
cli.showHelp();
|
|
41
|
-
return runtime.proc.exit(0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const command = positionals[0];
|
|
45
|
-
if (!definition.commands.some((c) => c.name === command)) {
|
|
46
|
-
cli.usageError(`unknown command "${command}"`);
|
|
47
|
-
return runtime.proc.exit(2);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Every command except `init` operates on an existing wiki tree. When it is
|
|
51
|
-
// absent (e.g. a fresh worktree where bootstrap.sh never ran), degrade
|
|
52
|
-
// gracefully: warn and exit 0 so the session Stop hook and other callers do
|
|
53
|
-
// not fail loudly. `init` is exempt — it creates the tree.
|
|
54
|
-
if (command !== "init") {
|
|
55
|
-
const wikiDir = resolveWikiRoot(runtime, parsed.values);
|
|
56
|
-
if (!wikiExists(runtime, wikiDir)) {
|
|
57
|
-
createLogger("wiki", runtime).warn(
|
|
58
|
-
command,
|
|
59
|
-
`no wiki at ${wikiDir}; skipping (run \`fit-wiki init\` to create one)`,
|
|
60
|
-
);
|
|
61
|
-
return runtime.proc.exit(0);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const gitClient = new GitClient({ runtime });
|
|
66
|
-
let wikiSync;
|
|
67
|
-
if (NEEDS_WIKI_SYNC.has(command)) {
|
|
68
|
-
const projectRoot = resolveProjectRoot(runtime);
|
|
69
|
-
const wikiDir = resolveWikiRoot(runtime, parsed.values);
|
|
70
|
-
const config = await createScriptConfig("wiki");
|
|
71
|
-
wikiSync = new WikiSync({
|
|
72
|
-
runtime,
|
|
73
|
-
gitClient,
|
|
74
|
-
wikiDir,
|
|
75
|
-
parentDir: projectRoot,
|
|
76
|
-
resolveToken: () => config.ghToken(),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let ghClient;
|
|
81
|
-
if (NEEDS_GH_CLIENT.has(command)) {
|
|
82
|
-
ghClient = new GhClient({ runtime });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const result = await cli.dispatch(parsed, {
|
|
86
|
-
deps: { runtime, wikiSync, gitClient, ghClient },
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const envelope = result ?? { ok: true };
|
|
90
|
-
if (!envelope.ok && envelope.error) cli.usageError(envelope.error);
|
|
91
|
-
runtime.proc.exit(envelope.ok ? 0 : (envelope.code ?? 1));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
main();
|