djournal 0.3.1 → 0.4.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/.agents/adapters/pi/journal-hook.js +89 -0
- package/.pi/extensions/djournal.ts +6 -0
- package/README.md +10 -3
- package/bin/journal.js +6 -3
- package/docs/architecture.md +6 -0
- package/docs/installation.md +12 -2
- package/docs/uninstalling.md +3 -1
- package/lib/installer/index.js +31 -5
- package/package.json +3 -2
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { handle } = require("../shared/journal-hook.js");
|
|
4
|
+
|
|
5
|
+
function contextFrom(output) {
|
|
6
|
+
return output?.hookSpecificOutput?.additionalContext || "";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function assistantText(message) {
|
|
10
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content)) return null;
|
|
11
|
+
const parts = message.content
|
|
12
|
+
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
13
|
+
.map((part) => part.text);
|
|
14
|
+
return parts.length ? parts.join("\n") : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function registerPiAdapter(pi, options = {}) {
|
|
18
|
+
const checker = options.handle || handle;
|
|
19
|
+
const state = {
|
|
20
|
+
sessionContext: "",
|
|
21
|
+
closureRetryActive: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function warn(ctx, error) {
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
+
ctx?.ui?.notify?.(`djournal hook skipped: ${message}`, "warning");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function check(payload, ctx) {
|
|
30
|
+
try {
|
|
31
|
+
return checker(payload) || {};
|
|
32
|
+
} catch (error) {
|
|
33
|
+
warn(ctx, error);
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pi.on("session_start", (_event, ctx) => {
|
|
39
|
+
state.sessionContext = contextFrom(check({
|
|
40
|
+
cwd: ctx.cwd,
|
|
41
|
+
hook_event_name: "SessionStart",
|
|
42
|
+
}, ctx));
|
|
43
|
+
state.closureRetryActive = false;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
47
|
+
const promptContext = contextFrom(check({
|
|
48
|
+
cwd: ctx.cwd,
|
|
49
|
+
hook_event_name: "UserPromptSubmit",
|
|
50
|
+
prompt: event.prompt,
|
|
51
|
+
}, ctx));
|
|
52
|
+
const additionalContext = [state.sessionContext, promptContext].filter(Boolean).join("\n\n");
|
|
53
|
+
if (!additionalContext) return undefined;
|
|
54
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${additionalContext}` };
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
pi.on("turn_end", (event, ctx) => {
|
|
58
|
+
if (event.message?.role !== "assistant" || event.message.stopReason !== "stop") return;
|
|
59
|
+
const lastAssistantMessage = assistantText(event.message);
|
|
60
|
+
if (lastAssistantMessage === null) return;
|
|
61
|
+
const retryWasActive = state.closureRetryActive;
|
|
62
|
+
const output = check({
|
|
63
|
+
cwd: ctx.cwd,
|
|
64
|
+
hook_event_name: "Stop",
|
|
65
|
+
last_assistant_message: lastAssistantMessage,
|
|
66
|
+
stop_hook_active: retryWasActive,
|
|
67
|
+
}, ctx);
|
|
68
|
+
|
|
69
|
+
if (retryWasActive) {
|
|
70
|
+
state.closureRetryActive = false;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (output.decision !== "block" || typeof output.reason !== "string" || !output.reason) return;
|
|
74
|
+
|
|
75
|
+
state.closureRetryActive = true;
|
|
76
|
+
try {
|
|
77
|
+
pi.sendUserMessage(output.reason, { deliverAs: "followUp" });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
state.closureRetryActive = false;
|
|
80
|
+
warn(ctx, error);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return state;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = registerPiAdapter;
|
|
88
|
+
module.exports.assistantText = assistantText;
|
|
89
|
+
module.exports.registerPiAdapter = registerPiAdapter;
|
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ flowchart LR
|
|
|
112
112
|
- `AGENTS.md` supplies portable workflow instructions.
|
|
113
113
|
- Skills handle planning, research, decisions, documentation, recall, audit,
|
|
114
114
|
reconciliation, and session closure.
|
|
115
|
-
- Codex
|
|
115
|
+
- Codex, Claude Code, and Pi integrations provide reminders and closure validation.
|
|
116
116
|
- Hooks never create or modify semantic journal entries.
|
|
117
117
|
- Read-only and trivial requests do not generate unnecessary ceremony.
|
|
118
118
|
|
|
@@ -166,12 +166,13 @@ djournal install
|
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
The global install gives you the `djournal` and `journal` commands. The
|
|
169
|
-
installer targets the current directory and detects Codex
|
|
169
|
+
installer targets the current directory and detects Codex, Claude Code, or Pi. Select
|
|
170
170
|
explicitly when needed:
|
|
171
171
|
|
|
172
172
|
```bash
|
|
173
173
|
djournal install --harness codex
|
|
174
174
|
djournal install --harness claude-code
|
|
175
|
+
djournal install --harness pi
|
|
175
176
|
djournal install --all
|
|
176
177
|
```
|
|
177
178
|
|
|
@@ -202,6 +203,12 @@ commands used by the workflow. Codex uses runtime sandbox configuration for
|
|
|
202
203
|
filesystem access, so sandboxed Codex sessions must include the generated store
|
|
203
204
|
path when they need canonical journal memory.
|
|
204
205
|
|
|
206
|
+
Pi loads the managed `.pi/extensions/djournal.ts` integration only after the
|
|
207
|
+
project is trusted. Approve the project interactively with `/trust` and restart
|
|
208
|
+
Pi, or use `pi --approve` for a non-interactive run. Pi has no built-in
|
|
209
|
+
filesystem sandbox; when an external sandbox or container is used, expose the
|
|
210
|
+
global store referenced by `.djournal.json`.
|
|
211
|
+
|
|
205
212
|
## Lifecycle
|
|
206
213
|
|
|
207
214
|
```bash
|
|
@@ -230,7 +237,7 @@ dedicated journal repository.
|
|
|
230
237
|
|
|
231
238
|
## Status
|
|
232
239
|
|
|
233
|
-
Codex
|
|
240
|
+
Codex, Claude Code, and Pi are supported. OpenCode and Zed adapters are planned.
|
|
234
241
|
|
|
235
242
|
Git-backed sharing and automatic sync are opt-in so local-only work remains
|
|
236
243
|
private by default.
|
package/bin/journal.js
CHANGED
|
@@ -33,7 +33,7 @@ Options:
|
|
|
33
33
|
--yes Disable interactive harness selection
|
|
34
34
|
--json Emit JSON
|
|
35
35
|
--work SLUG Select a journal work item instead of active state
|
|
36
|
-
--harness LIST Comma-separated codex,claude-code selection
|
|
36
|
+
--harness LIST Comma-separated codex,claude-code,pi selection
|
|
37
37
|
--all Select all work items for share, or every harness
|
|
38
38
|
--instructions-only Install core instructions without harness hooks
|
|
39
39
|
`;
|
|
@@ -104,7 +104,10 @@ function print(value, json) {
|
|
|
104
104
|
}
|
|
105
105
|
if (value.conflicts?.length) process.stdout.write(`conflicts: ${value.conflicts.join(", ")}\n`);
|
|
106
106
|
if (value.files) {
|
|
107
|
-
for (const file of value.files)
|
|
107
|
+
for (const file of value.files) {
|
|
108
|
+
if (typeof file === "string") process.stdout.write(`${file}\n`);
|
|
109
|
+
else process.stdout.write(`${file.status.padEnd(8)} ${file.path}\n`);
|
|
110
|
+
}
|
|
108
111
|
}
|
|
109
112
|
if (value.checks) {
|
|
110
113
|
for (const check of value.checks) process.stdout.write(`${check.ok ? "ok" : "fail"} ${check.name}: ${check.detail}\n`);
|
|
@@ -144,4 +147,4 @@ async function main() {
|
|
|
144
147
|
|
|
145
148
|
if (require.main === module) main();
|
|
146
149
|
|
|
147
|
-
module.exports = { parseArgs };
|
|
150
|
+
module.exports = { parseArgs, print };
|
package/docs/architecture.md
CHANGED
|
@@ -168,6 +168,12 @@ For Claude Code, install also injects a narrow permission grant into
|
|
|
168
168
|
runtime sandbox, so Codex sessions must be launched with the global store path
|
|
169
169
|
available when sandboxing would otherwise hide `~/.djournal`.
|
|
170
170
|
|
|
171
|
+
Pi uses a trusted project extension at `.pi/extensions/djournal.ts`. A thin Pi
|
|
172
|
+
adapter maps `session_start`, `before_agent_start`, and final `turn_end` events
|
|
173
|
+
into the shared checker. Because Pi has no blocking stop hook,
|
|
174
|
+
an invalid final marker causes at most one follow-up turn. Pi project trust is
|
|
175
|
+
managed by Pi, and external sandboxes must expose the global store.
|
|
176
|
+
|
|
171
177
|
## Related docs
|
|
172
178
|
|
|
173
179
|
- [Djournal in practice](djournal-in-practice.md)
|
package/docs/installation.md
CHANGED
|
@@ -42,6 +42,7 @@ This installs:
|
|
|
42
42
|
|
|
43
43
|
- shared journal rules and skills under `.agents/`
|
|
44
44
|
- harness-specific hook config when selected
|
|
45
|
+
- a managed `.pi/extensions/djournal.ts` extension when Pi is selected
|
|
45
46
|
- managed instruction blocks in `AGENTS.md` and/or `CLAUDE.md`
|
|
46
47
|
- `.djournal.json`, which points to the global project store
|
|
47
48
|
- for Claude Code installs, a narrow permission grant that lets the agent read
|
|
@@ -64,6 +65,12 @@ rather than `.codex/hooks.json`. The installed Codex hook resolves the global
|
|
|
64
65
|
store through `.djournal.json`; if the Codex session is sandboxed, launch it
|
|
65
66
|
with the generated journal store path available as a readable/writable root.
|
|
66
67
|
|
|
68
|
+
Pi loads project `.agents/skills` and `.pi/extensions/` only after project
|
|
69
|
+
trust. In interactive Pi, use `/trust` and restart the session. For print, JSON,
|
|
70
|
+
or RPC runs without a stored trust decision, pass `--approve`. djournal does not
|
|
71
|
+
edit Pi's trust file. Pi itself is not a filesystem sandbox; external containers
|
|
72
|
+
or sandboxes must expose the global store referenced by `.djournal.json`.
|
|
73
|
+
|
|
67
74
|
To share selected work through the product repository, enable colocated
|
|
68
75
|
projection:
|
|
69
76
|
|
|
@@ -116,12 +123,13 @@ Install for one harness:
|
|
|
116
123
|
```bash
|
|
117
124
|
djournal install --harness codex
|
|
118
125
|
djournal install --harness claude-code
|
|
126
|
+
djournal install --harness pi
|
|
119
127
|
```
|
|
120
128
|
|
|
121
129
|
Install for multiple harnesses:
|
|
122
130
|
|
|
123
131
|
```bash
|
|
124
|
-
djournal install --harness codex,claude-code
|
|
132
|
+
djournal install --harness codex,claude-code,pi
|
|
125
133
|
```
|
|
126
134
|
|
|
127
135
|
Install every supported harness:
|
|
@@ -144,7 +152,9 @@ djournal doctor
|
|
|
144
152
|
```
|
|
145
153
|
|
|
146
154
|
`status` reports installed files and cleanliness. `doctor` checks the local
|
|
147
|
-
environment and harness configuration.
|
|
155
|
+
environment and harness configuration. For Pi it reports extension presence
|
|
156
|
+
and reminds you that project trust is required; it does not inspect or change
|
|
157
|
+
Pi's private trust state.
|
|
148
158
|
|
|
149
159
|
## Related docs
|
|
150
160
|
|
package/docs/uninstalling.md
CHANGED
|
@@ -24,12 +24,13 @@ so.
|
|
|
24
24
|
```bash
|
|
25
25
|
djournal uninstall --harness codex
|
|
26
26
|
djournal uninstall --harness claude-code
|
|
27
|
+
djournal uninstall --harness pi
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
## Uninstall multiple harnesses
|
|
30
31
|
|
|
31
32
|
```bash
|
|
32
|
-
djournal uninstall --harness codex,claude-code
|
|
33
|
+
djournal uninstall --harness codex,claude-code,pi
|
|
33
34
|
```
|
|
34
35
|
|
|
35
36
|
Partial uninstall removes only the selected harness integration and keeps the
|
|
@@ -58,6 +59,7 @@ or select a harness:
|
|
|
58
59
|
|
|
59
60
|
```bash
|
|
60
61
|
djournal install --harness codex
|
|
62
|
+
djournal install --harness pi
|
|
61
63
|
```
|
|
62
64
|
|
|
63
65
|
Reinstalling restores local harness integration around the existing journal
|
package/lib/installer/index.js
CHANGED
|
@@ -16,8 +16,12 @@ const {
|
|
|
16
16
|
} = require("./merge.js");
|
|
17
17
|
|
|
18
18
|
const SCHEMA_VERSION = 1;
|
|
19
|
-
const SUPPORTED_HARNESSES = ["codex", "claude-code"];
|
|
20
|
-
const FUTURE_HARNESSES = ["opencode", "
|
|
19
|
+
const SUPPORTED_HARNESSES = ["codex", "claude-code", "pi"];
|
|
20
|
+
const FUTURE_HARNESSES = ["opencode", "zed"];
|
|
21
|
+
const HARNESS_CONFIG_PATHS = {
|
|
22
|
+
codex: ".codex/hooks.json",
|
|
23
|
+
"claude-code": ".claude/settings.json",
|
|
24
|
+
};
|
|
21
25
|
const MANIFEST_PATH = ".journal/.install/manifest.json";
|
|
22
26
|
const TRANSACTION_PATH = ".journal/.install/transaction.json";
|
|
23
27
|
const PROJECT_MARKER_PATH = ".djournal.json";
|
|
@@ -452,7 +456,7 @@ function jsonRecord(target, relative, fragment, oldRecord, harness) {
|
|
|
452
456
|
};
|
|
453
457
|
}
|
|
454
458
|
|
|
455
|
-
function copyRecord(sourceRoot, target, relative, oldRecord) {
|
|
459
|
+
function copyRecord(sourceRoot, target, relative, oldRecord, harness) {
|
|
456
460
|
const source = fs.readFileSync(path.join(sourceRoot, relative));
|
|
457
461
|
const absolute = resolveWithin(target, relative);
|
|
458
462
|
const current = readOptional(absolute);
|
|
@@ -468,6 +472,7 @@ function copyRecord(sourceRoot, target, relative, oldRecord) {
|
|
|
468
472
|
record: {
|
|
469
473
|
path: relative,
|
|
470
474
|
mode: "copy",
|
|
475
|
+
...(harness ? { harness } : {}),
|
|
471
476
|
created: oldRecord?.created ?? current === null,
|
|
472
477
|
installedHash: sha256(source),
|
|
473
478
|
},
|
|
@@ -475,7 +480,8 @@ function copyRecord(sourceRoot, target, relative, oldRecord) {
|
|
|
475
480
|
}
|
|
476
481
|
|
|
477
482
|
function harnessFragment(sourceRoot, harness) {
|
|
478
|
-
const relative = harness
|
|
483
|
+
const relative = HARNESS_CONFIG_PATHS[harness];
|
|
484
|
+
if (!relative) throw new InstallerError(`harness has no JSON fragment: ${harness}`, "UNSUPPORTED_HARNESS");
|
|
479
485
|
return parseJson(fs.readFileSync(path.join(sourceRoot, relative)), relative);
|
|
480
486
|
}
|
|
481
487
|
|
|
@@ -507,6 +513,16 @@ function planInstall(sourceRoot, target, harnesses, oldManifest, store) {
|
|
|
507
513
|
);
|
|
508
514
|
records.push(hooks.record); operations.push(hooks.op);
|
|
509
515
|
}
|
|
516
|
+
if (harnesses.includes("pi")) {
|
|
517
|
+
const extension = copyRecord(
|
|
518
|
+
sourceRoot,
|
|
519
|
+
target,
|
|
520
|
+
".pi/extensions/djournal.ts",
|
|
521
|
+
old.get(".pi/extensions/djournal.ts"),
|
|
522
|
+
"pi",
|
|
523
|
+
);
|
|
524
|
+
records.push(extension.record); operations.push(extension.op);
|
|
525
|
+
}
|
|
510
526
|
|
|
511
527
|
const desired = new Set(records.map((record) => record.path));
|
|
512
528
|
for (const record of oldManifest?.files || []) {
|
|
@@ -828,10 +844,20 @@ function doctor(options) {
|
|
|
828
844
|
}
|
|
829
845
|
}
|
|
830
846
|
const manifest = readOptional(resolveWithin(target, MANIFEST_PATH));
|
|
847
|
+
let loadedManifest = null;
|
|
831
848
|
if (manifest) {
|
|
832
|
-
try { loadManifest(target); checks.push({ name: "manifest", ok: true, detail: "supported" }); }
|
|
849
|
+
try { loadedManifest = loadManifest(target); checks.push({ name: "manifest", ok: true, detail: "supported" }); }
|
|
833
850
|
catch (error) { checks.push({ name: "manifest", ok: false, detail: error.message }); }
|
|
834
851
|
}
|
|
852
|
+
if (loadedManifest?.harnesses?.includes("pi")) {
|
|
853
|
+
const record = loadedManifest.files.find((item) => item.path === ".pi/extensions/djournal.ts" && item.harness === "pi");
|
|
854
|
+
const assetStatus = record ? recordStatus(target, record) : "missing";
|
|
855
|
+
checks.push({
|
|
856
|
+
name: ".pi/extensions/djournal.ts",
|
|
857
|
+
ok: assetStatus === "clean" || assetStatus === "modified",
|
|
858
|
+
detail: `${assetStatus}; Pi project trust required`,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
835
861
|
return { ok: checks.every((check) => check.ok), target, detected, checks };
|
|
836
862
|
}
|
|
837
863
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "djournal",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Durable project memory for coding agents, stored as linked Markdown",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
".agents/",
|
|
12
12
|
".claude/",
|
|
13
13
|
".codex/",
|
|
14
|
+
".pi/",
|
|
14
15
|
"AGENTS.md",
|
|
15
16
|
"CLAUDE.md",
|
|
16
17
|
"LICENSE",
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
"check:pr-title": "node scripts/check-pr-title.js",
|
|
26
27
|
"link:global": "npm link",
|
|
27
28
|
"release": "semantic-release",
|
|
28
|
-
"test": "node tests/journal-hook.test.js && node tests/installer/installer.test.js && node tests/release-policy.test.js && npm run check:package",
|
|
29
|
+
"test": "node tests/journal-hook.test.js && node tests/pi-adapter.test.js && node tests/installer/installer.test.js && node tests/release-policy.test.js && npm run check:package",
|
|
29
30
|
"prepublishOnly": "npm test"
|
|
30
31
|
},
|
|
31
32
|
"repository": {
|