@deltafleet/codex-goalkeeper 0.1.0 → 0.1.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/CHANGELOG.md +6 -0
- package/README.ja.md +116 -109
- package/README.ko.md +115 -108
- package/README.md +104 -123
- package/README.zh-CN.md +130 -123
- package/docs/RELEASE.md +6 -4
- package/docs/ROADMAP.md +2 -2
- package/package.json +9 -11
- package/{SKILL.md → src/codex-goalkeeper/SKILL.md} +13 -13
- package/src/{references → codex-goalkeeper/references}/event-schema.md +1 -1
- package/src/{references → codex-goalkeeper/references}/guardrail.md +7 -7
- package/src/{references → codex-goalkeeper/references}/workflow.md +7 -7
- package/src/{scripts → codex-goalkeeper/scripts}/goalkeeper-append-event.mjs +1 -1
- package/src/{scripts → codex-goalkeeper/scripts}/goalkeeper-doctor.mjs +1 -1
- package/src/{scripts → codex-goalkeeper/scripts}/goalkeeper-init.mjs +1 -1
- package/src/{scripts → codex-goalkeeper/scripts}/goalkeeper-turn-start.mjs +1 -1
- package/src/{scripts → codex-goalkeeper/scripts}/goalkeeper-update-checkpoint.mjs +1 -1
- package/src/{templates → codex-goalkeeper/templates}/AGENTS.goalkeeper.md +7 -7
- package/src/scripts/test-goalkeeper-update-checkpoint.mjs +0 -236
- /package/{agents → src/codex-goalkeeper/agents}/openai.yaml +0 -0
- /package/src/{templates → codex-goalkeeper/templates}/active-session +0 -0
- /package/src/{templates → codex-goalkeeper/templates}/checkpoint.md +0 -0
- /package/src/{templates → codex-goalkeeper/templates}/context-pack.md +0 -0
- /package/src/{templates → codex-goalkeeper/templates}/event.jsonl +0 -0
package/docs/ROADMAP.md
CHANGED
|
@@ -8,7 +8,7 @@ Goalkeeper should stay boring: a short checkpoint, a medium-density context pack
|
|
|
8
8
|
|
|
9
9
|
## MVP
|
|
10
10
|
|
|
11
|
-
Ship a
|
|
11
|
+
Ship a Codex skill under `src/codex-goalkeeper/` that manages project-local state:
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
14
|
.goalkeeper/
|
|
@@ -41,7 +41,7 @@ Keep these scripts central:
|
|
|
41
41
|
|
|
42
42
|
Keep this optional and maintainer-oriented:
|
|
43
43
|
|
|
44
|
-
- `test-goalkeeper-update-checkpoint.mjs`
|
|
44
|
+
- `tests/test-goalkeeper-update-checkpoint.mjs`
|
|
45
45
|
|
|
46
46
|
## Non-Goals
|
|
47
47
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deltafleet/codex-goalkeeper",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A small Codex skill for keeping long-running goals oriented across compaction, resumes, and handoffs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,16 +22,14 @@
|
|
|
22
22
|
"goal"
|
|
23
23
|
],
|
|
24
24
|
"bin": {
|
|
25
|
-
"codex-goalkeeper-init": "src/scripts/goalkeeper-init.mjs",
|
|
26
|
-
"codex-goalkeeper-turn-start": "src/scripts/goalkeeper-turn-start.mjs",
|
|
27
|
-
"codex-goalkeeper-append-event": "src/scripts/goalkeeper-append-event.mjs",
|
|
28
|
-
"codex-goalkeeper-update-checkpoint": "src/scripts/goalkeeper-update-checkpoint.mjs",
|
|
29
|
-
"codex-goalkeeper-doctor": "src/scripts/goalkeeper-doctor.mjs"
|
|
25
|
+
"codex-goalkeeper-init": "src/codex-goalkeeper/scripts/goalkeeper-init.mjs",
|
|
26
|
+
"codex-goalkeeper-turn-start": "src/codex-goalkeeper/scripts/goalkeeper-turn-start.mjs",
|
|
27
|
+
"codex-goalkeeper-append-event": "src/codex-goalkeeper/scripts/goalkeeper-append-event.mjs",
|
|
28
|
+
"codex-goalkeeper-update-checkpoint": "src/codex-goalkeeper/scripts/goalkeeper-update-checkpoint.mjs",
|
|
29
|
+
"codex-goalkeeper-doctor": "src/codex-goalkeeper/scripts/goalkeeper-doctor.mjs"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
|
-
"
|
|
33
|
-
"agents",
|
|
34
|
-
"src",
|
|
32
|
+
"src/codex-goalkeeper",
|
|
35
33
|
"examples",
|
|
36
34
|
"docs",
|
|
37
35
|
"README.md",
|
|
@@ -45,8 +43,8 @@
|
|
|
45
43
|
"CODE_OF_CONDUCT.md"
|
|
46
44
|
],
|
|
47
45
|
"scripts": {
|
|
48
|
-
"check:scripts": "find src/scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check",
|
|
49
|
-
"test": "node
|
|
46
|
+
"check:scripts": "find src/codex-goalkeeper/scripts tests -name '*.mjs' -print0 | xargs -0 -n1 node --check",
|
|
47
|
+
"test": "node tests/test-goalkeeper-update-checkpoint.mjs",
|
|
50
48
|
"validate:skill": "npx skills add . --list",
|
|
51
49
|
"validate:examples": "find examples -name '*.jsonl' -print0 | xargs -0 -n1 jq -c . >/dev/null",
|
|
52
50
|
"validate": "npm run check:scripts && npm test && npm run validate:examples && npm run validate:skill"
|
|
@@ -45,7 +45,7 @@ Use a project-local `.goalkeeper/` directory. Each long-running goal session get
|
|
|
45
45
|
|
|
46
46
|
Use a stable, readable `<goal-session-id>` such as `2026-05-17-codex-goalkeeper-roadmap` or `ads-ops-release-hardening`.
|
|
47
47
|
|
|
48
|
-
If any core file is missing during long goal work, create it from the templates in `
|
|
48
|
+
If any core file is missing during long goal work, create it from the templates in `templates/`.
|
|
49
49
|
Use `.goalkeeper/active-session` when a workspace has one active Goalkeeper session and the agent should not have to reconstruct the session id after compaction.
|
|
50
50
|
|
|
51
51
|
The directory is created inside the active project workspace, not in a global Codex directory. Example:
|
|
@@ -57,19 +57,19 @@ The directory is created inside the active project workspace, not in a global Co
|
|
|
57
57
|
Bundled scripts live in the skill package. When the script is not located inside the target workspace, pass the target workspace explicitly:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
node <skill-path>/
|
|
60
|
+
node <skill-path>/scripts/goalkeeper-turn-start.mjs --workspace <workspace> --session <goal-session-id>
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
If `<workspace>/.goalkeeper/active-session` points to the session id, `--session` may be omitted:
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
node <skill-path>/
|
|
66
|
+
node <skill-path>/scripts/goalkeeper-turn-start.mjs --workspace <workspace>
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
To create a new session deterministically, run:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
node <skill-path>/
|
|
72
|
+
node <skill-path>/scripts/goalkeeper-init.mjs --workspace <workspace> --session <goal-session-id> --goal "<active goal>"
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
## Recovery Rule
|
|
@@ -109,7 +109,7 @@ Update Goalkeeper state when any of these change:
|
|
|
109
109
|
- handoff boundary
|
|
110
110
|
|
|
111
111
|
Append the event first, then update the session's `checkpoint.md` when the event changes the current working state.
|
|
112
|
-
Use `
|
|
112
|
+
Use `scripts/goalkeeper-update-checkpoint.mjs` when you want a bounded canonical checkpoint instead of manual Markdown edits.
|
|
113
113
|
|
|
114
114
|
## Keep It Short
|
|
115
115
|
|
|
@@ -148,14 +148,14 @@ Read it when checkpoint recovery is not enough. Keep raw transcripts and long co
|
|
|
148
148
|
|
|
149
149
|
## Reference Map
|
|
150
150
|
|
|
151
|
-
- Read `
|
|
152
|
-
- Read `
|
|
153
|
-
- Read `
|
|
154
|
-
- Run `
|
|
155
|
-
- Run `
|
|
156
|
-
- Run `
|
|
157
|
-
- Run `
|
|
158
|
-
- Run `
|
|
151
|
+
- Read `references/workflow.md` for lifecycle rules and examples.
|
|
152
|
+
- Read `references/event-schema.md` before adding or validating event records.
|
|
153
|
+
- Read `references/guardrail.md` when you need stronger always-on behavior in a target repository.
|
|
154
|
+
- Run `scripts/goalkeeper-init.mjs` when a long-running goal needs a new `.goalkeeper/sessions/<goal-session-id>/` directory.
|
|
155
|
+
- Run `scripts/goalkeeper-turn-start.mjs --context` when checkpoint recovery needs the larger context pack too.
|
|
156
|
+
- Run `scripts/goalkeeper-append-event.mjs` instead of hand-writing JSONL when recording decisions, verification, failures, risks, or handoffs; it can use `.goalkeeper/active-session` when `--session` is omitted.
|
|
157
|
+
- Run `scripts/goalkeeper-update-checkpoint.mjs` after appending a meaningful event when checkpoint state should be refreshed in a short canonical shape.
|
|
158
|
+
- Run `scripts/goalkeeper-doctor.mjs` after creating or changing Goalkeeper state to verify the target workspace is ready.
|
|
159
159
|
|
|
160
160
|
## Safety Boundary
|
|
161
161
|
|
|
@@ -11,7 +11,7 @@ Default path:
|
|
|
11
11
|
Prefer the append helper when available:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
node <skill-path>/
|
|
14
|
+
node <skill-path>/scripts/goalkeeper-append-event.mjs --workspace <workspace> --session <goal-session-id> --type decision --text "<summary>"
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
When `<workspace>/.goalkeeper/active-session` points to the current session, `--session` may be omitted. The helper validates existing JSONL schema before writing and reports the appended line number.
|
|
@@ -5,7 +5,7 @@ The skill body is not enough to guarantee checkpoint-first behavior after compac
|
|
|
5
5
|
For high-stakes long-running work, add the AGENTS guardrail template to the target workspace:
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
|
-
|
|
8
|
+
templates/AGENTS.goalkeeper.md
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Use it in one of these ways:
|
|
@@ -29,34 +29,34 @@ For an active Goalkeeper-managed goal:
|
|
|
29
29
|
3. Use `events.jsonl` only when exact evidence is needed.
|
|
30
30
|
4. Append `recovery_violation` if the agent continued after compaction or resume before reading the checkpoint.
|
|
31
31
|
|
|
32
|
-
If `
|
|
32
|
+
If `scripts/goalkeeper-turn-start.mjs` is present, it can be used as the first recovery action:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
node
|
|
35
|
+
node scripts/goalkeeper-turn-start.mjs --session <goal-session-id>
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
If `.goalkeeper/active-session` contains the current session id, this shorter form is valid:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
node
|
|
41
|
+
node scripts/goalkeeper-turn-start.mjs
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
If the helper is being run from an installed skill package rather than from the target repository, pass the target workspace explicitly:
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
node <skill-path>/
|
|
47
|
+
node <skill-path>/scripts/goalkeeper-turn-start.mjs --workspace <workspace> --session <goal-session-id>
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
Add `--context` when the medium-density context pack is needed:
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
node <skill-path>/
|
|
53
|
+
node <skill-path>/scripts/goalkeeper-turn-start.mjs --workspace <workspace> --session <goal-session-id> --context
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
Before starting a high-stakes long run, use the read-only doctor to verify the target workspace has the required state and guardrail:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
node <skill-path>/
|
|
59
|
+
node <skill-path>/scripts/goalkeeper-doctor.mjs --workspace <workspace> --session <goal-session-id> --strict
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
Parallel calls are still subject to checkpoint-first ordering. It is acceptable to batch `pwd`, `.goalkeeper/sessions` discovery, and `goalkeeper-turn-start.mjs`; it is not acceptable to include normal project files or verification in that same first post-compact parallel call.
|
|
@@ -17,7 +17,7 @@ When a user starts a long-running goal:
|
|
|
17
17
|
Use the init helper when available:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
node <skill-path>/
|
|
20
|
+
node <skill-path>/scripts/goalkeeper-init.mjs --workspace <workspace> --session <goal-session-id> --goal "<active goal>"
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Pass repeated `--constraint "<text>"` flags for known durable constraints. The helper refuses to overwrite an existing session unless `--force` is explicitly provided.
|
|
@@ -37,7 +37,7 @@ During normal work:
|
|
|
37
37
|
Use the append helper for routine event writes:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
node <skill-path>/
|
|
40
|
+
node <skill-path>/scripts/goalkeeper-append-event.mjs --workspace <workspace> --session <goal-session-id> --type verification --text "<summary>"
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
If `.goalkeeper/active-session` points to the target session, `--session` may be omitted. The helper reports the appended JSONL line number so later checkpoint evidence can cite the event precisely.
|
|
@@ -45,7 +45,7 @@ If `.goalkeeper/active-session` points to the target session, `--session` may be
|
|
|
45
45
|
When the event changes the recoverable working state, refresh the checkpoint in the same working segment:
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
node <skill-path>/
|
|
48
|
+
node <skill-path>/scripts/goalkeeper-update-checkpoint.mjs \
|
|
49
49
|
--workspace <workspace> \
|
|
50
50
|
--session <goal-session-id> \
|
|
51
51
|
--goal "<active goal>" \
|
|
@@ -84,19 +84,19 @@ sed -n '1,220p' .goalkeeper/sessions/<goal-session-id>/checkpoint.md
|
|
|
84
84
|
If the turn-start helper is available, use it instead of manually reading the checkpoint:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
node <skill-path>/
|
|
87
|
+
node <skill-path>/scripts/goalkeeper-turn-start.mjs --workspace <workspace> --session <goal-session-id>
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
If `.goalkeeper/active-session` points to the correct session id, omit `--session`:
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
|
-
node <skill-path>/
|
|
93
|
+
node <skill-path>/scripts/goalkeeper-turn-start.mjs --workspace <workspace>
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
If checkpoint recovery is too thin, include the context pack:
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
|
-
node <skill-path>/
|
|
99
|
+
node <skill-path>/scripts/goalkeeper-turn-start.mjs --workspace <workspace> --context
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
## Resume After Compaction
|
|
@@ -133,7 +133,7 @@ If an agent violates this order, append a `recovery_violation` event, read the c
|
|
|
133
133
|
Before relying on a workspace for a long run, run the read-only doctor:
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
|
-
node <skill-path>/
|
|
136
|
+
node <skill-path>/scripts/goalkeeper-doctor.mjs --workspace <workspace> --session <goal-session-id> --strict
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
## Goal Session Directory
|
|
@@ -22,7 +22,7 @@ const EVENT_TYPES = new Set([
|
|
|
22
22
|
const STATUSES = new Set(["open", "done", "failed", "blocked", "superseded"]);
|
|
23
23
|
|
|
24
24
|
const USAGE = `Usage:
|
|
25
|
-
node
|
|
25
|
+
node scripts/goalkeeper-append-event.mjs --type <event-type> --text <summary> [--session <goal-session-id>] [--workspace <path>] [--goal <text>] [--reason <text>] [--evidence <text>] [--status <status>] [--file <path> ...] [--command <cmd> ...] [--ts <iso>] [--json]
|
|
26
26
|
|
|
27
27
|
Appends one validated JSONL event to <workspace>/.goalkeeper/sessions/<goal-session-id>/events.jsonl.
|
|
28
28
|
This script writes only to the target events.jsonl file.
|
|
@@ -28,7 +28,7 @@ const CONTEXT_PACK_TARGET_BYTES = 30_000;
|
|
|
28
28
|
const CONTEXT_PACK_MAX_BYTES = 60_000;
|
|
29
29
|
|
|
30
30
|
const USAGE = `Usage:
|
|
31
|
-
node
|
|
31
|
+
node scripts/goalkeeper-doctor.mjs --session <goal-session-id> [--workspace <path>] [--strict] [--json]
|
|
32
32
|
|
|
33
33
|
Checks whether a target workspace has enough Goalkeeper state and guardrails for long-running Codex goal work.
|
|
34
34
|
This script is read-only.
|
|
@@ -4,7 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
const USAGE = `Usage:
|
|
7
|
-
node
|
|
7
|
+
node scripts/goalkeeper-init.mjs --session <goal-session-id> --goal <text> [--workspace <path>] [--constraint <text> ...] [--no-activate] [--force] [--json]
|
|
8
8
|
|
|
9
9
|
Creates a project-local Goalkeeper session with checkpoint.md, context-pack.md, and events.jsonl.
|
|
10
10
|
This script writes only under <workspace>/.goalkeeper/sessions/<goal-session-id>/.
|
|
@@ -4,7 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
const USAGE = `Usage:
|
|
7
|
-
node
|
|
7
|
+
node scripts/goalkeeper-turn-start.mjs [--session <goal-session-id>] [--workspace <path>] [--events <n>] [--context] [--json]
|
|
8
8
|
|
|
9
9
|
Reads the active Goalkeeper checkpoint at the start of a Codex turn.
|
|
10
10
|
This script reads only .goalkeeper state.
|
|
@@ -7,7 +7,7 @@ const DEFAULT_MAX_BYTES = 8_000;
|
|
|
7
7
|
const HARD_MAX_BYTES = 16_000;
|
|
8
8
|
|
|
9
9
|
const USAGE = `Usage:
|
|
10
|
-
node
|
|
10
|
+
node scripts/goalkeeper-update-checkpoint.mjs --goal <text> --next <text> [--session <goal-session-id>] [--workspace <path>] [--done <text>] [--status <text>] [--throughline <text>] [--why <text>] [--constraint <text> ...] [--forbidden <text> ...] [--decision <text> ...] [--attempt <text> ...] [--file <path> ...] [--verified <text> ...] [--unverified <text> ...] [--risk <text> ...] [--evidence <text> ...] [--max-bytes <n>] [--dry-run] [--json]
|
|
11
11
|
|
|
12
12
|
Replaces checkpoint.md with a bounded, canonical recovery checkpoint.
|
|
13
13
|
Append the corresponding event first with goalkeeper-append-event.mjs; this script writes only checkpoint.md.
|
|
@@ -10,22 +10,22 @@ At the start of each new assistant turn, before reading normal project files or
|
|
|
10
10
|
4. If the checkpoint is unclear or too thin, read `.goalkeeper/sessions/<goal-session-id>/context-pack.md`.
|
|
11
11
|
5. If exact evidence is needed, inspect recent `.goalkeeper/sessions/<goal-session-id>/events.jsonl` entries.
|
|
12
12
|
|
|
13
|
-
If this repository includes `
|
|
13
|
+
If this repository includes `scripts/goalkeeper-turn-start.mjs`, you may use:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
node
|
|
16
|
+
node scripts/goalkeeper-turn-start.mjs --session <goal-session-id>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
If `.goalkeeper/active-session` contains the current session id, this shorter form is also valid:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
node
|
|
22
|
+
node scripts/goalkeeper-turn-start.mjs
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
If the helper comes from an installed skill path instead of this repository, pass the target workspace:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
node <skill-path>/
|
|
28
|
+
node <skill-path>/scripts/goalkeeper-turn-start.mjs --workspace <workspace> --session <goal-session-id>
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
If checkpoint recovery needs the larger context pack too, add `--context`.
|
|
@@ -36,9 +36,9 @@ Allowed before reading the checkpoint:
|
|
|
36
36
|
- listing `.goalkeeper/sessions/`
|
|
37
37
|
- reading `.goalkeeper/active-session`
|
|
38
38
|
- minimal filename inspection needed to choose the active session
|
|
39
|
-
- running `node
|
|
40
|
-
- running `node
|
|
41
|
-
- running `node <skill-path>/
|
|
39
|
+
- running `node scripts/goalkeeper-turn-start.mjs --session <goal-session-id>`
|
|
40
|
+
- running `node scripts/goalkeeper-turn-start.mjs`
|
|
41
|
+
- running `node <skill-path>/scripts/goalkeeper-turn-start.mjs --workspace <workspace> --session <goal-session-id>`
|
|
42
42
|
- adding `--context` to the turn-start command when the checkpoint is too thin
|
|
43
43
|
|
|
44
44
|
Do not read project docs, source files, examples, tests, or make edits before the checkpoint read.
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
|
|
8
|
-
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const REPO_ROOT = path.resolve(SCRIPT_DIR, "..", "..");
|
|
10
|
-
const TMP_ROOT = path.join(REPO_ROOT, ".goalkeeper", "tmp", "checkpoint-update");
|
|
11
|
-
const WORKSPACE = path.join(TMP_ROOT, "workspace");
|
|
12
|
-
const SESSION_ID = "checkpoint-update-poc";
|
|
13
|
-
|
|
14
|
-
function run(args, options = {}) {
|
|
15
|
-
const result = spawnSync(process.execPath, args, {
|
|
16
|
-
cwd: REPO_ROOT,
|
|
17
|
-
encoding: "utf8",
|
|
18
|
-
...options,
|
|
19
|
-
});
|
|
20
|
-
return {
|
|
21
|
-
...result,
|
|
22
|
-
stdout: result.stdout.trim(),
|
|
23
|
-
stderr: result.stderr.trim(),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function script(name) {
|
|
28
|
-
return path.join(SCRIPT_DIR, name);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function assert(condition, message) {
|
|
32
|
-
if (!condition) {
|
|
33
|
-
throw new Error(message);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function setupWorkspace() {
|
|
38
|
-
fs.rmSync(TMP_ROOT, { recursive: true, force: true });
|
|
39
|
-
fs.mkdirSync(WORKSPACE, { recursive: true });
|
|
40
|
-
fs.writeFileSync(
|
|
41
|
-
path.join(WORKSPACE, "AGENTS.md"),
|
|
42
|
-
[
|
|
43
|
-
"# Goalkeeper Guardrail",
|
|
44
|
-
"",
|
|
45
|
-
"At the start of each new assistant turn, before source work, read .goalkeeper/sessions/<goal-session-id>/checkpoint.md.",
|
|
46
|
-
"This checkpoint-first rule applies after compaction, compact, start, resume, and before normal project work.",
|
|
47
|
-
"",
|
|
48
|
-
].join("\n"),
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function main() {
|
|
53
|
-
setupWorkspace();
|
|
54
|
-
|
|
55
|
-
const init = run([
|
|
56
|
-
script("goalkeeper-init.mjs"),
|
|
57
|
-
"--workspace",
|
|
58
|
-
WORKSPACE,
|
|
59
|
-
"--session",
|
|
60
|
-
SESSION_ID,
|
|
61
|
-
"--goal",
|
|
62
|
-
"Validate canonical checkpoint updates.",
|
|
63
|
-
"--constraint",
|
|
64
|
-
"Keep checkpoints short.",
|
|
65
|
-
"--json",
|
|
66
|
-
]);
|
|
67
|
-
assert(init.status === 0, `init failed:\n${init.stderr}\n${init.stdout}`);
|
|
68
|
-
|
|
69
|
-
const append = run([
|
|
70
|
-
script("goalkeeper-append-event.mjs"),
|
|
71
|
-
"--workspace",
|
|
72
|
-
WORKSPACE,
|
|
73
|
-
"--type",
|
|
74
|
-
"decision",
|
|
75
|
-
"--text",
|
|
76
|
-
"Use the update helper to rewrite checkpoint.md canonically.",
|
|
77
|
-
"--json",
|
|
78
|
-
]);
|
|
79
|
-
assert(append.status === 0, `append failed:\n${append.stderr}\n${append.stdout}`);
|
|
80
|
-
const parsedAppend = JSON.parse(append.stdout);
|
|
81
|
-
assert(parsedAppend.sessionId === SESSION_ID, "append helper should recover the session id from active-session");
|
|
82
|
-
assert(parsedAppend.lineNumber === 4, "append helper should report the appended JSONL line number");
|
|
83
|
-
|
|
84
|
-
const contextPackPath = path.join(WORKSPACE, ".goalkeeper", "sessions", SESSION_ID, "context-pack.md");
|
|
85
|
-
assert(fs.existsSync(contextPackPath), "init should create context-pack.md");
|
|
86
|
-
|
|
87
|
-
const turnStartWithContext = run([
|
|
88
|
-
script("goalkeeper-turn-start.mjs"),
|
|
89
|
-
"--workspace",
|
|
90
|
-
WORKSPACE,
|
|
91
|
-
"--context",
|
|
92
|
-
"--json",
|
|
93
|
-
]);
|
|
94
|
-
assert(turnStartWithContext.status === 0, `turn-start --context failed:\n${turnStartWithContext.stderr}\n${turnStartWithContext.stdout}`);
|
|
95
|
-
const parsedTurnStart = JSON.parse(turnStartWithContext.stdout);
|
|
96
|
-
assert(parsedTurnStart.contextPackPath === contextPackPath, "turn-start should report context-pack.md path");
|
|
97
|
-
assert(parsedTurnStart.contextPack.includes("Context Pack"), "turn-start --context should include context pack content");
|
|
98
|
-
|
|
99
|
-
const noActiveWorkspace = path.join(TMP_ROOT, "no-active-workspace");
|
|
100
|
-
fs.mkdirSync(path.join(noActiveWorkspace, ".goalkeeper", "sessions", SESSION_ID), { recursive: true });
|
|
101
|
-
fs.writeFileSync(path.join(noActiveWorkspace, ".goalkeeper", "sessions", SESSION_ID, "events.jsonl"), "");
|
|
102
|
-
const appendWithoutActive = run([
|
|
103
|
-
script("goalkeeper-append-event.mjs"),
|
|
104
|
-
"--workspace",
|
|
105
|
-
noActiveWorkspace,
|
|
106
|
-
"--type",
|
|
107
|
-
"decision",
|
|
108
|
-
"--text",
|
|
109
|
-
"This should fail because no active-session pointer exists.",
|
|
110
|
-
"--json",
|
|
111
|
-
]);
|
|
112
|
-
assert(appendWithoutActive.status === 1, "append without --session should fail when active-session is missing");
|
|
113
|
-
|
|
114
|
-
const schemaInvalidWorkspace = path.join(TMP_ROOT, "schema-invalid-workspace");
|
|
115
|
-
const schemaInvalidSessionDir = path.join(schemaInvalidWorkspace, ".goalkeeper", "sessions", SESSION_ID);
|
|
116
|
-
fs.mkdirSync(schemaInvalidSessionDir, { recursive: true });
|
|
117
|
-
fs.writeFileSync(path.join(schemaInvalidWorkspace, ".goalkeeper", "active-session"), `${SESSION_ID}\n`);
|
|
118
|
-
fs.writeFileSync(
|
|
119
|
-
path.join(schemaInvalidSessionDir, "events.jsonl"),
|
|
120
|
-
`${JSON.stringify({ ts: "2026-05-18T00:00:00Z", type: "unknown", text: "bad existing event" })}\n`,
|
|
121
|
-
);
|
|
122
|
-
const appendToSchemaInvalid = run([
|
|
123
|
-
script("goalkeeper-append-event.mjs"),
|
|
124
|
-
"--workspace",
|
|
125
|
-
schemaInvalidWorkspace,
|
|
126
|
-
"--type",
|
|
127
|
-
"decision",
|
|
128
|
-
"--text",
|
|
129
|
-
"This should fail because the existing event log is schema-invalid.",
|
|
130
|
-
"--json",
|
|
131
|
-
]);
|
|
132
|
-
assert(appendToSchemaInvalid.status === 1, "append should refuse an existing schema-invalid event log");
|
|
133
|
-
|
|
134
|
-
const update = run([
|
|
135
|
-
script("goalkeeper-update-checkpoint.mjs"),
|
|
136
|
-
"--workspace",
|
|
137
|
-
WORKSPACE,
|
|
138
|
-
"--goal",
|
|
139
|
-
"Validate canonical checkpoint updates.",
|
|
140
|
-
"--done",
|
|
141
|
-
"Doctor passes after a helper-rendered checkpoint.",
|
|
142
|
-
"--status",
|
|
143
|
-
"Helper under test.",
|
|
144
|
-
"--throughline",
|
|
145
|
-
"Use deterministic CLI rendering instead of manual checkpoint Markdown.",
|
|
146
|
-
"--why",
|
|
147
|
-
"Long sessions need bounded state that can be safely refreshed after compacted turns.",
|
|
148
|
-
"--constraint",
|
|
149
|
-
"Keep checkpoint under the routine-read budget.",
|
|
150
|
-
"--forbidden",
|
|
151
|
-
"Do not paste long command output into checkpoint.md.",
|
|
152
|
-
"--decision",
|
|
153
|
-
"Render a canonical checkpoint from CLI fields.",
|
|
154
|
-
"--attempt",
|
|
155
|
-
"Manual checkpoint edits remain possible but are not the default path.",
|
|
156
|
-
"--file",
|
|
157
|
-
"src/scripts/goalkeeper-update-checkpoint.mjs",
|
|
158
|
-
"--file",
|
|
159
|
-
"docs/path with spaces.md",
|
|
160
|
-
"--evidence",
|
|
161
|
-
"This test rewrites checkpoint.md in a guarded temporary workspace.",
|
|
162
|
-
"--verified",
|
|
163
|
-
"Update helper exits 0.",
|
|
164
|
-
"--unverified",
|
|
165
|
-
"No real compact boundary is generated by this unit test.",
|
|
166
|
-
"--risk",
|
|
167
|
-
"Overlong input should fail before writing.",
|
|
168
|
-
"--next",
|
|
169
|
-
"Run strict doctor against the updated temporary workspace.",
|
|
170
|
-
"--json",
|
|
171
|
-
]);
|
|
172
|
-
assert(update.status === 0, `update failed:\n${update.stderr}\n${update.stdout}`);
|
|
173
|
-
|
|
174
|
-
const parsedUpdate = JSON.parse(update.stdout);
|
|
175
|
-
assert(parsedUpdate.bytes > 0 && parsedUpdate.bytes <= 8_000, "updated checkpoint should fit the default budget");
|
|
176
|
-
|
|
177
|
-
const checkpointPath = path.join(WORKSPACE, ".goalkeeper", "sessions", SESSION_ID, "checkpoint.md");
|
|
178
|
-
const checkpoint = fs.readFileSync(checkpointPath, "utf8");
|
|
179
|
-
assert(checkpoint.includes("## Active Goal"), "checkpoint should include Active Goal section");
|
|
180
|
-
assert(checkpoint.includes("## Context Pack"), "checkpoint should include Context Pack section");
|
|
181
|
-
assert(checkpoint.includes("## Next Action"), "checkpoint should include Next Action section");
|
|
182
|
-
assert(checkpoint.includes("src/scripts/goalkeeper-update-checkpoint.mjs"), "checkpoint should include important files");
|
|
183
|
-
assert(checkpoint.includes("docs/path with spaces.md"), "checkpoint should preserve spaces in file paths");
|
|
184
|
-
|
|
185
|
-
const beforeOversize = checkpoint;
|
|
186
|
-
const oversize = run([
|
|
187
|
-
script("goalkeeper-update-checkpoint.mjs"),
|
|
188
|
-
"--workspace",
|
|
189
|
-
WORKSPACE,
|
|
190
|
-
"--goal",
|
|
191
|
-
"Validate oversize refusal.",
|
|
192
|
-
"--decision",
|
|
193
|
-
"x".repeat(2_000),
|
|
194
|
-
"--next",
|
|
195
|
-
"This should not be written.",
|
|
196
|
-
"--max-bytes",
|
|
197
|
-
"1000",
|
|
198
|
-
"--json",
|
|
199
|
-
]);
|
|
200
|
-
assert(oversize.status === 1, "oversize checkpoint update should exit 1");
|
|
201
|
-
assert(fs.readFileSync(checkpointPath, "utf8") === beforeOversize, "oversize failure should not rewrite checkpoint.md");
|
|
202
|
-
|
|
203
|
-
const doctor = run([
|
|
204
|
-
script("goalkeeper-doctor.mjs"),
|
|
205
|
-
"--workspace",
|
|
206
|
-
WORKSPACE,
|
|
207
|
-
"--session",
|
|
208
|
-
SESSION_ID,
|
|
209
|
-
"--strict",
|
|
210
|
-
"--json",
|
|
211
|
-
]);
|
|
212
|
-
assert(doctor.status === 0, `doctor failed:\n${doctor.stderr}\n${doctor.stdout}`);
|
|
213
|
-
const parsedDoctor = JSON.parse(doctor.stdout);
|
|
214
|
-
assert(parsedDoctor.ok === true, "doctor JSON should be ok");
|
|
215
|
-
|
|
216
|
-
console.log(
|
|
217
|
-
JSON.stringify(
|
|
218
|
-
{
|
|
219
|
-
ok: true,
|
|
220
|
-
workspace: WORKSPACE,
|
|
221
|
-
sessionId: SESSION_ID,
|
|
222
|
-
checkpointBytes: parsedUpdate.bytes,
|
|
223
|
-
doctor: parsedDoctor.summary,
|
|
224
|
-
},
|
|
225
|
-
null,
|
|
226
|
-
2,
|
|
227
|
-
),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
main();
|
|
233
|
-
} catch (error) {
|
|
234
|
-
console.error(error.message);
|
|
235
|
-
process.exit(1);
|
|
236
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|