@forwardimpact/libwiki 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/fit-wiki.js CHANGED
@@ -192,8 +192,7 @@ const definition = {
192
192
  },
193
193
  {
194
194
  name: "init",
195
- description:
196
- "Bootstrap a wiki working tree, scaffold Active Claims, install audit Stop-hook",
195
+ description: "Bootstrap a wiki working tree and scaffold Active Claims",
197
196
  options: {
198
197
  ...wikiRootOpt,
199
198
  "skills-dir": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/libwiki",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Wiki lifecycle primitives — stable memory for agent teams so coordination persists across sessions.",
5
5
  "keywords": [
6
6
  "wiki",
@@ -55,66 +55,6 @@ function scaffoldActiveClaims(memoryPath) {
55
55
  return true;
56
56
  }
57
57
 
58
- const AUDIT_HOOK_COMMAND = "bunx fit-wiki audit";
59
-
60
- function readSettings(settingsPath) {
61
- if (!existsSync(settingsPath)) return {};
62
- try {
63
- return JSON.parse(readFileSync(settingsPath, "utf-8"));
64
- } catch {
65
- process.stderr.write(
66
- `init: ${settingsPath} is not valid JSON; skipping stop-hook install\n`,
67
- );
68
- return null;
69
- }
70
- }
71
-
72
- function hasAuditHook(settings) {
73
- if (!settings.hooks || !Array.isArray(settings.hooks.Stop)) return false;
74
- for (const group of settings.hooks.Stop) {
75
- if (!group || !Array.isArray(group.hooks)) continue;
76
- if (
77
- group.hooks.some(
78
- (h) =>
79
- h &&
80
- typeof h.command === "string" &&
81
- h.command.includes("fit-wiki audit"),
82
- )
83
- ) {
84
- return true;
85
- }
86
- }
87
- return false;
88
- }
89
-
90
- function addAuditHook(settings) {
91
- if (!settings.hooks) settings.hooks = {};
92
- if (!Array.isArray(settings.hooks.Stop)) settings.hooks.Stop = [];
93
- if (settings.hooks.Stop.length === 0) {
94
- settings.hooks.Stop.push({
95
- hooks: [{ type: "command", command: AUDIT_HOOK_COMMAND }],
96
- });
97
- return;
98
- }
99
- if (!Array.isArray(settings.hooks.Stop[0].hooks)) {
100
- settings.hooks.Stop[0].hooks = [];
101
- }
102
- settings.hooks.Stop[0].hooks.push({
103
- type: "command",
104
- command: AUDIT_HOOK_COMMAND,
105
- });
106
- }
107
-
108
- function installStopHook(settingsPath) {
109
- const settings = readSettings(settingsPath);
110
- if (settings === null) return false;
111
- if (hasAuditHook(settings)) return false;
112
- addAuditHook(settings);
113
- mkdirSync(path.dirname(settingsPath), { recursive: true });
114
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
115
- return true;
116
- }
117
-
118
58
  async function maybeCloneWiki(projectRoot, wikiDir) {
119
59
  const wikiUrl = deriveWikiUrl(projectRoot);
120
60
  if (!wikiUrl) {
@@ -139,7 +79,7 @@ async function maybeCloneWiki(projectRoot, wikiDir) {
139
79
  }
140
80
  }
141
81
 
142
- /** Clone the wiki if not already present, scaffold Active Claims in MEMORY.md, install the audit Stop-hook, and create per-skill metric directories. */
82
+ /** Clone the wiki if not already present, scaffold Active Claims in MEMORY.md, and create per-skill metric directories. */
143
83
  export async function runInitCommand(values, _args, _cli) {
144
84
  const logger = { debug() {} };
145
85
  const finder = new Finder(fsAsync, logger, process);
@@ -150,7 +90,6 @@ export async function runInitCommand(values, _args, _cli) {
150
90
  projectRoot,
151
91
  values["skills-dir"] ?? path.join(".claude", "skills"),
152
92
  );
153
- const settingsPath = path.resolve(projectRoot, ".claude", "settings.json");
154
93
 
155
94
  await maybeCloneWiki(projectRoot, wikiDir);
156
95
 
@@ -169,11 +108,5 @@ export async function runInitCommand(values, _args, _cli) {
169
108
  }
170
109
  }
171
110
 
172
- if (installStopHook(settingsPath)) {
173
- process.stdout.write(
174
- `init: installed Stop-hook audit entry in ${settingsPath}\n`,
175
- );
176
- }
177
-
178
111
  process.stdout.write(`init: wiki ready at ${wikiDir}\n`);
179
112
  }
@@ -1,3 +1,4 @@
1
+ import { existsSync, readFileSync } from "node:fs";
1
2
  import fsAsync from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { Finder } from "@forwardimpact/libutil";
@@ -28,10 +29,14 @@ function commonContext(values) {
28
29
  return { agent, wikiRoot, today };
29
30
  }
30
31
 
31
- function ensureDateHeading(body, today) {
32
- const heading = `## ${today}`;
33
- if (body.startsWith(heading) || body.startsWith("## ")) return body;
34
- return `${heading}\n\n${body}`;
32
+ function lastDateHeading(text) {
33
+ // Match `## YYYY-MM-DD` at the start of a line, optionally followed by
34
+ // suffix text (e.g. `## 2026-05-19 (third activation)`).
35
+ const re = /^## (\d{4}-\d{2}-\d{2})/gm;
36
+ let last = null;
37
+ let match;
38
+ while ((match = re.exec(text)) !== null) last = match[1];
39
+ return last;
35
40
  }
36
41
 
37
42
  function runDecision(values) {
@@ -67,13 +72,15 @@ function runNote(values) {
67
72
  process.stderr.write("log note requires --field and --body\n");
68
73
  process.exit(2);
69
74
  }
70
- const body = ensureDateHeading(
71
- `### ${values.field}\n\n${values.body}\n`,
72
- today,
73
- );
74
- const lineCount = body.split("\n").length;
75
- rotateIfOverBudget(wikiRoot, agent, today, lineCount);
75
+ const fieldBlock = `### ${values.field}\n\n${values.body}\n`;
76
+ // Conservative line budget: assume we'll prepend a date heading.
77
+ const withHeading = `## ${today}\n\n${fieldBlock}`;
78
+ rotateIfOverBudget(wikiRoot, agent, today, withHeading.split("\n").length);
76
79
  const target = weeklyLogPath(wikiRoot, agent, today);
80
+ // Append under the open entry if the file's last `## YYYY-MM-DD` is today;
81
+ // otherwise open a new entry by prepending a date heading.
82
+ const existing = existsSync(target) ? readFileSync(target, "utf-8") : "";
83
+ const body = lastDateHeading(existing) === today ? fieldBlock : withHeading;
77
84
  appendEntry(target, body, agent, today);
78
85
  process.stdout.write(`logged note to ${target}\n`);
79
86
  }