@forwardimpact/libwiki 0.2.0 → 0.2.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.
- package/bin/fit-wiki.js +1 -2
- package/package.json +1 -1
- package/src/commands/init.js +1 -68
- package/src/commands/log.js +17 -10
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
package/src/commands/init.js
CHANGED
|
@@ -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,
|
|
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
|
}
|
package/src/commands/log.js
CHANGED
|
@@ -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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
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
|
}
|