@forwardimpact/libwiki 0.1.2 → 0.1.3
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 +6 -2
- package/bin/fit-wiki.js +8 -2
- package/package.json +12 -12
- package/src/agent-roster.js +1 -0
- package/src/block-renderer.js +3 -0
- package/src/commands/init.js +1 -0
- package/src/commands/memo.js +47 -11
- package/src/commands/refresh.js +1 -0
- package/src/commands/sync.js +2 -0
- package/src/marker-migrator.js +1 -0
- package/src/marker-scanner.js +1 -0
- package/src/memo-writer.js +1 -0
- package/src/skill-roster.js +1 -0
- package/src/wiki-repo.js +12 -0
package/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# libwiki
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
<!-- BEGIN:description — Do not edit. Generated from package.json. -->
|
|
4
|
+
|
|
5
|
+
Wiki lifecycle primitives — stable memory for agent teams so coordination
|
|
6
|
+
persists across sessions.
|
|
7
|
+
|
|
8
|
+
<!-- END:description -->
|
|
5
9
|
|
|
6
10
|
## Getting Started
|
|
7
11
|
|
package/bin/fit-wiki.js
CHANGED
|
@@ -101,8 +101,14 @@ const definition = {
|
|
|
101
101
|
],
|
|
102
102
|
documentation: [
|
|
103
103
|
{
|
|
104
|
-
title: "
|
|
105
|
-
url: "https://www.forwardimpact.team/docs/libraries/
|
|
104
|
+
title: "Operate a Predictable Agent Team",
|
|
105
|
+
url: "https://www.forwardimpact.team/docs/libraries/predictable-team/index.md",
|
|
106
|
+
description:
|
|
107
|
+
"End-to-end guide to wiki memory, XmR charts, and team coordination.",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
title: "Send a Memo or Update a Storyboard",
|
|
111
|
+
url: "https://www.forwardimpact.team/docs/libraries/predictable-team/wiki-operations/index.md",
|
|
106
112
|
description:
|
|
107
113
|
"Send cross-team memos, refresh storyboard charts, and sync the wiki.",
|
|
108
114
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/libwiki",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Wiki lifecycle primitives
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Wiki lifecycle primitives — stable memory for agent teams so coordination persists across sessions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"wiki",
|
|
7
7
|
"memo",
|
|
@@ -17,16 +17,16 @@
|
|
|
17
17
|
},
|
|
18
18
|
"license": "Apache-2.0",
|
|
19
19
|
"author": "D. Olsson <hi@senzilla.io>",
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
"jobs": [
|
|
21
|
+
{
|
|
22
|
+
"user": "Empowered Engineers",
|
|
23
|
+
"goal": "Operate a Predictable Agent Team",
|
|
24
|
+
"trigger": "An agent finishes a session and its findings vanish because there is no shared memory to write them to.",
|
|
25
|
+
"bigHire": "give agent teams stable memory that persists across sessions.",
|
|
26
|
+
"littleHire": "send a memo or update a storyboard without managing the wiki infrastructure.",
|
|
27
|
+
"competesWith": "git commit messages as memory; ephemeral conversation context; starting every session from scratch"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
30
|
"type": "module",
|
|
31
31
|
"main": "./src/index.js",
|
|
32
32
|
"exports": {
|
package/src/agent-roster.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readdirSync, statSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { BROADCAST_TARGET } from "./constants.js";
|
|
4
4
|
|
|
5
|
+
/** List all agent markdown files in the agents directory, returning agent names and summary paths. */
|
|
5
6
|
export function listAgents(
|
|
6
7
|
{ agentsDir, wikiRoot },
|
|
7
8
|
fs = { readdirSync, statSync },
|
package/src/block-renderer.js
CHANGED
|
@@ -2,13 +2,16 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { analyze, renderChart, MIN_POINTS } from "@forwardimpact/libxmr";
|
|
4
4
|
|
|
5
|
+
/** Error thrown when an XmR block cannot be rendered due to missing CSV or metric. */
|
|
5
6
|
export class BlockRenderError extends Error {
|
|
7
|
+
/** Create a BlockRenderError with the given reason string. */
|
|
6
8
|
constructor(reason) {
|
|
7
9
|
super(reason);
|
|
8
10
|
this.name = "BlockRenderError";
|
|
9
11
|
}
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
/** Render an XmR chart block for a metric by reading its CSV and producing markdown lines. */
|
|
12
15
|
export function renderBlock({
|
|
13
16
|
metric,
|
|
14
17
|
csvPath,
|
package/src/commands/init.js
CHANGED
|
@@ -17,6 +17,7 @@ function deriveWikiUrl(parentDir) {
|
|
|
17
17
|
return base + ".wiki.git";
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Clone the wiki if not already present (URL derived from origin remote), copy git identity from the parent repo, and create metric directories for each kata skill. */
|
|
20
21
|
export function runInitCommand(values, _args, cli) {
|
|
21
22
|
const logger = { debug() {} };
|
|
22
23
|
const finder = new Finder(fsAsync, logger, process);
|
package/src/commands/memo.js
CHANGED
|
@@ -15,6 +15,38 @@ function writeAndCheck(summaryPath, sender, message, today) {
|
|
|
15
15
|
process.stdout.write(`wrote ${result.path}\n`);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function resolveTargetPath(wikiRoot, target) {
|
|
19
|
+
const summaryPath = path.join(wikiRoot, target + ".md");
|
|
20
|
+
const resolvedRoot = path.resolve(wikiRoot);
|
|
21
|
+
const resolvedTarget = path.resolve(summaryPath);
|
|
22
|
+
const relative = path.relative(resolvedRoot, resolvedTarget);
|
|
23
|
+
const escapesRoot =
|
|
24
|
+
relative === "" || relative.startsWith("..") || path.isAbsolute(relative);
|
|
25
|
+
return { summaryPath, escapesRoot };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function writeSingleTarget({ wikiRoot, target, sender, message, today, cli }) {
|
|
29
|
+
const { summaryPath, escapesRoot } = resolveTargetPath(wikiRoot, target);
|
|
30
|
+
if (escapesRoot) {
|
|
31
|
+
cli.usageError(`target escapes wiki root: ${target}`);
|
|
32
|
+
process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
if (!existsSync(summaryPath)) {
|
|
35
|
+
cli.usageError(`target summary not found: ${summaryPath}`);
|
|
36
|
+
process.exit(2);
|
|
37
|
+
}
|
|
38
|
+
writeAndCheck(summaryPath, sender, message, today);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeBroadcast({ agentsDir, wikiRoot, sender, message, today }) {
|
|
42
|
+
const agents = listAgents({ agentsDir, wikiRoot });
|
|
43
|
+
for (const { agent, summaryPath } of agents) {
|
|
44
|
+
if (agent === sender) continue;
|
|
45
|
+
writeAndCheck(summaryPath, sender, message, today);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Write a memo to a target agent's summary file (or broadcast to all except the sender); sender is --from or LIBEVAL_AGENT_PROFILE env var. */
|
|
18
50
|
export function runMemoCommand(values, _args, cli) {
|
|
19
51
|
const sender = values.from || process.env.LIBEVAL_AGENT_PROFILE;
|
|
20
52
|
|
|
@@ -44,17 +76,21 @@ export function runMemoCommand(values, _args, cli) {
|
|
|
44
76
|
const today = new Date().toISOString().slice(0, 10);
|
|
45
77
|
|
|
46
78
|
if (values.to === BROADCAST_TARGET) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
79
|
+
writeBroadcast({
|
|
80
|
+
agentsDir,
|
|
81
|
+
wikiRoot,
|
|
82
|
+
sender,
|
|
83
|
+
message: values.message,
|
|
84
|
+
today,
|
|
85
|
+
});
|
|
52
86
|
} else {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
87
|
+
writeSingleTarget({
|
|
88
|
+
wikiRoot,
|
|
89
|
+
target: values.to,
|
|
90
|
+
sender,
|
|
91
|
+
message: values.message,
|
|
92
|
+
today,
|
|
93
|
+
cli,
|
|
94
|
+
});
|
|
59
95
|
}
|
|
60
96
|
}
|
package/src/commands/refresh.js
CHANGED
|
@@ -12,6 +12,7 @@ function currentStoryboardPath() {
|
|
|
12
12
|
return `wiki/storyboard-${yyyy}-M${mm}.md`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** Re-render all XmR chart blocks in a storyboard file by scanning markers and splicing updated content. */
|
|
15
16
|
export function runRefreshCommand(values, args, cli) {
|
|
16
17
|
const logger = { debug() {} };
|
|
17
18
|
const finder = new Finder(fsAsync, logger, process);
|
package/src/commands/sync.js
CHANGED
|
@@ -3,6 +3,7 @@ import fsAsync from "node:fs/promises";
|
|
|
3
3
|
import { Finder } from "@forwardimpact/libutil";
|
|
4
4
|
import { WikiRepo, WikiPullConflict } from "../wiki-repo.js";
|
|
5
5
|
|
|
6
|
+
/** Commit all wiki changes and push them to the remote wiki repository. */
|
|
6
7
|
export function runPushCommand(values, _args, cli) {
|
|
7
8
|
const logger = { debug() {} };
|
|
8
9
|
const finder = new Finder(fsAsync, logger, process);
|
|
@@ -20,6 +21,7 @@ export function runPushCommand(values, _args, cli) {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
/** Fetch and rebase the local wiki on origin/master; on rebase conflict, exit the process with code 1 and a message to resolve manually or push first. */
|
|
23
25
|
export function runPullCommand(values, _args, cli) {
|
|
24
26
|
const logger = { debug() {} };
|
|
25
27
|
const finder = new Finder(fsAsync, logger, process);
|
package/src/marker-migrator.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { MEMO_INBOX_MARKER, INBOX_HEADING } from "./constants.js";
|
|
3
3
|
import { listAgents } from "./agent-roster.js";
|
|
4
4
|
|
|
5
|
+
/** Insert memo:inbox markers into agent summary files that have an inbox heading but no marker yet. */
|
|
5
6
|
export function insertMarkers(
|
|
6
7
|
{ agentsDir, wikiRoot },
|
|
7
8
|
fs = { readFileSync, writeFileSync },
|
package/src/marker-scanner.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const OPEN_RE = /^<!--\s*xmr:([^:\s]+):([^\s]+)\s*-->\s*$/;
|
|
2
2
|
const CLOSE_RE = /^<!--\s*\/xmr\s*-->\s*$/;
|
|
3
3
|
|
|
4
|
+
/** Scan text for paired xmr open/close HTML comment markers and return their line positions and metadata. */
|
|
4
5
|
export function scanMarkers(text) {
|
|
5
6
|
const lines = text.split("\n");
|
|
6
7
|
const pairs = [];
|
package/src/memo-writer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { MEMO_INBOX_MARKER } from "./constants.js";
|
|
3
3
|
|
|
4
|
+
/** Append a timestamped memo bullet below the inbox marker in an agent's summary file. */
|
|
4
5
|
export function writeMemo(
|
|
5
6
|
{ summaryPath, sender, message, today },
|
|
6
7
|
fs = { readFileSync, writeFileSync },
|
package/src/skill-roster.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdirSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
+
/** List all kata-prefixed skill directory names under the skills directory, sorted alphabetically. */
|
|
4
5
|
export function listSkills({ skillsDir }, fs = { readdirSync, statSync }) {
|
|
5
6
|
const entries = fs.readdirSync(skillsDir);
|
|
6
7
|
const skills = [];
|
package/src/wiki-repo.js
CHANGED
|
@@ -3,7 +3,9 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
const CREDENTIAL_HELPER_BODY =
|
|
4
4
|
'!f() { echo username=x-access-token; echo "password=${GH_TOKEN:-$GITHUB_TOKEN}"; }; f';
|
|
5
5
|
|
|
6
|
+
/** Error thrown when a wiki pull encounters a rebase conflict that cannot be resolved automatically. */
|
|
6
7
|
export class WikiPullConflict extends Error {
|
|
8
|
+
/** Create a WikiPullConflict with the stderr output from the failed rebase. */
|
|
7
9
|
constructor(stderr) {
|
|
8
10
|
super("rebase conflict on pull");
|
|
9
11
|
this.name = "WikiPullConflict";
|
|
@@ -11,6 +13,7 @@ export class WikiPullConflict extends Error {
|
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
/** Prepend credential-helper config arguments to a git command when a token is available. */
|
|
14
17
|
export function buildAuthArgs(args, token) {
|
|
15
18
|
if (token) {
|
|
16
19
|
return [
|
|
@@ -24,15 +27,18 @@ export function buildAuthArgs(args, token) {
|
|
|
24
27
|
return [...args];
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
/** Git operations wrapper for the GitHub wiki repository used as agent team memory. */
|
|
27
31
|
export class WikiRepo {
|
|
28
32
|
#wikiDir;
|
|
29
33
|
#parentDir;
|
|
30
34
|
|
|
35
|
+
/** Create a WikiRepo targeting the given wiki directory and its parent project directory. */
|
|
31
36
|
constructor({ wikiDir, parentDir }) {
|
|
32
37
|
this.#wikiDir = wikiDir;
|
|
33
38
|
this.#parentDir = parentDir;
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
/** Check whether the wiki directory is an initialized git repository. */
|
|
36
42
|
isCloned() {
|
|
37
43
|
const r = spawnSync(
|
|
38
44
|
"git",
|
|
@@ -44,6 +50,7 @@ export class WikiRepo {
|
|
|
44
50
|
return r.status === 0;
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/** Clone the wiki from the given URL if it is not already cloned. */
|
|
47
54
|
ensureCloned(url) {
|
|
48
55
|
if (this.isCloned()) return { cloned: true, reason: "already-cloned" };
|
|
49
56
|
const r = this.#authGit(["clone", url, this.#wikiDir]);
|
|
@@ -56,6 +63,7 @@ export class WikiRepo {
|
|
|
56
63
|
return { cloned: true, reason: "cloned" };
|
|
57
64
|
}
|
|
58
65
|
|
|
66
|
+
/** Copy git user.name and user.email from the parent repository into the wiki repository. */
|
|
59
67
|
inheritIdentity() {
|
|
60
68
|
const name = this.#parentConfig("user.name");
|
|
61
69
|
const email = this.#parentConfig("user.email");
|
|
@@ -63,15 +71,18 @@ export class WikiRepo {
|
|
|
63
71
|
if (email) this.#git(["config", "user.email", email]);
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
/** Fetch the latest master branch from the wiki remote using token auth if available. */
|
|
66
75
|
fetch() {
|
|
67
76
|
this.#authGit(["-C", this.#wikiDir, "fetch", "origin", "master"]);
|
|
68
77
|
}
|
|
69
78
|
|
|
79
|
+
/** Return true if the wiki working tree has no uncommitted changes. */
|
|
70
80
|
isClean() {
|
|
71
81
|
const r = this.#git(["status", "--porcelain"]);
|
|
72
82
|
return r.stdout.toString().trim() === "";
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
/** Fetch and rebase on origin/master, throwing WikiPullConflict if the rebase fails. */
|
|
75
86
|
pull() {
|
|
76
87
|
this.fetch();
|
|
77
88
|
const r = this.#git(["rebase", "origin/master"]);
|
|
@@ -81,6 +92,7 @@ export class WikiRepo {
|
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
|
|
95
|
+
/** Stage all changes, commit with the given message, fetch, rebase on origin/master (falling back to a merge with -X ours if rebase fails), and push. */
|
|
84
96
|
commitAndPush(message) {
|
|
85
97
|
if (this.isClean()) return { pushed: false, reason: "clean" };
|
|
86
98
|
this.#git(["add", "-A"]);
|