portable-agent-layer 0.114.0-canary.9 → 0.114.0
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 +22 -15
- package/assets/plugins/dark-factory/README.md +39 -3
- package/assets/plugins/dark-factory/agents/coach.md +21 -0
- package/assets/plugins/dark-factory/agents/code-writer.md +1 -1
- package/assets/plugins/dark-factory/agents/debugger.md +4 -1
- package/assets/plugins/dark-factory/agents/reviewer.md +3 -1
- package/assets/plugins/dark-factory/scripts/build.mjs +68 -26
- package/assets/plugins/dark-factory/scripts/coach.mjs +473 -0
- package/assets/plugins/dark-factory/scripts/corrections.mjs +108 -0
- package/assets/plugins/dark-factory/scripts/coverage.mjs +61 -0
- package/assets/plugins/dark-factory/scripts/findings.mjs +288 -0
- package/assets/plugins/dark-factory/scripts/instructions.mjs +94 -0
- package/assets/plugins/dark-factory/scripts/lib.mjs +25 -3
- package/assets/plugins/dark-factory/scripts/role.mjs +54 -2
- package/assets/plugins/dark-factory/scripts/roles.mjs +112 -19
- package/assets/plugins/dark-factory/scripts/ship.mjs +4 -3
- package/assets/plugins/dark-factory/scripts/stats.mjs +42 -0
- package/assets/plugins/dark-factory/scripts/task.mjs +15 -1
- package/assets/plugins/dark-factory/skills/po/SKILL.md +8 -0
- package/assets/schema/pal-settings.schema.json +4 -17
- package/assets/skills/pal-analyze/SKILL.md +4 -4
- package/assets/skills/projects/SKILL.md +2 -3
- package/assets/templates/AGENTS.md.template +1 -2
- package/assets/templates/PAL/ALGORITHM.md +10 -81
- package/assets/templates/PAL/MEMORY_SYSTEM.md +12 -27
- package/assets/templates/PAL/PROJECT_LIFECYCLE.md +1 -2
- package/assets/templates/PAL/README.md +8 -16
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +37 -89
- package/assets/templates/pal-settings.json +0 -8
- package/assets/templates/rules.codex.rules +4 -12
- package/assets/templates/settings.antigravity.json +1 -5
- package/assets/templates/settings.claude.json +12 -6
- package/package.json +10 -10
- package/src/cli/builtin-tools.ts +0 -10
- package/src/cli/dependencies.ts +6 -1
- package/src/cli/index.ts +37 -17
- package/src/cli/leftovers.ts +115 -2
- package/src/cli/migrate.ts +180 -1
- package/src/cli/progress.ts +21 -0
- package/src/cli/tree.ts +5 -1
- package/src/cli/update-flow.ts +40 -11
- package/src/hooks/CompactRecover.ts +0 -1
- package/src/hooks/LoadContext.ts +2 -3
- package/src/hooks/PreCompactPersist.ts +1 -3
- package/src/hooks/RtkWrap.ts +4 -4
- package/src/hooks/SecurityValidator.ts +1 -4
- package/src/hooks/StopOrchestrator.ts +2 -9
- package/src/hooks/handlers/agenda.ts +1 -60
- package/src/hooks/handlers/inject-retrieval.ts +5 -31
- package/src/hooks/handlers/persist-last-exchange.ts +5 -165
- package/src/hooks/handlers/project-touch.ts +2 -12
- package/src/hooks/handlers/rating.ts +5 -41
- package/src/hooks/handlers/session-intelligence.ts +13 -66
- package/src/hooks/handlers/session-name.ts +0 -6
- package/src/hooks/handlers/synthesis.ts +0 -1
- package/src/hooks/handlers/tab.ts +0 -1
- package/src/hooks/handlers/update-check.ts +22 -5
- package/src/hooks/lib/capture-store.ts +13 -27
- package/src/hooks/lib/claim-check.ts +4 -10
- package/src/hooks/lib/claim-log.ts +11 -2
- package/src/hooks/lib/claude-md.ts +5 -14
- package/src/hooks/lib/context.ts +5 -161
- package/src/hooks/lib/daily-nudge.ts +0 -2
- package/src/hooks/lib/graduation.ts +16 -47
- package/src/hooks/lib/import-merge.ts +1 -1
- package/src/hooks/lib/learning-store.ts +1 -68
- package/src/hooks/lib/models.ts +1 -5
- package/src/hooks/lib/paths.ts +0 -5
- package/src/hooks/lib/post-tool.ts +2 -0
- package/src/hooks/lib/progress-tree-context.ts +6 -4
- package/src/hooks/lib/progress-tree-factory.ts +124 -0
- package/src/hooks/lib/progress-tree-git.ts +19 -4
- package/src/hooks/lib/progress-tree-prs.ts +214 -0
- package/src/hooks/lib/progress-tree-render.ts +83 -13
- package/src/hooks/lib/progress-tree-session-end.ts +61 -0
- package/src/hooks/lib/progress-tree-session-node.ts +11 -4
- package/src/hooks/lib/progress-tree-stop.ts +14 -0
- package/src/hooks/lib/progress-tree-tidy.ts +112 -0
- package/src/hooks/lib/progress-tree-tool.ts +174 -0
- package/src/hooks/lib/progress-tree.ts +113 -6
- package/src/hooks/lib/projects.ts +3 -7
- package/src/hooks/lib/prompt-text.ts +7 -3
- package/src/hooks/lib/rtk-worktree.ts +18 -0
- package/src/hooks/lib/security.ts +51 -7
- package/src/hooks/lib/semi-static.ts +1 -82
- package/src/hooks/lib/session-context.ts +1 -6
- package/src/hooks/lib/settings.ts +2 -0
- package/src/hooks/lib/stop.ts +2 -49
- package/src/hooks/lib/text-similarity.ts +2 -3
- package/src/hooks/lib/time.ts +0 -15
- package/src/hooks/lib/tool-events.ts +3 -3
- package/src/hooks/lib/transcript.ts +20 -1
- package/src/targets/claude/install.ts +0 -6
- package/src/targets/claude/uninstall.ts +0 -7
- package/src/targets/codex/install.ts +0 -6
- package/src/targets/codex/uninstall.ts +0 -5
- package/src/targets/copilot/install.ts +2 -10
- package/src/targets/copilot/uninstall.ts +0 -6
- package/src/targets/cursor/install.ts +0 -5
- package/src/targets/cursor/uninstall.ts +0 -6
- package/src/targets/lib.ts +2 -3
- package/src/targets/opencode/install.ts +0 -5
- package/src/targets/opencode/plugin.ts +17 -17
- package/src/targets/opencode/uninstall.ts +0 -5
- package/src/tools/agent/analyze.ts +3 -3
- package/src/tools/agent/project.ts +0 -12
- package/src/tools/agent/synthesize.ts +9 -53
- package/src/tools/control-room/attention.ts +13 -23
- package/src/tools/control-room/data.ts +9 -73
- package/src/tools/control-room/detail.ts +3 -24
- package/src/tools/control-room/matrix.ts +10 -11
- package/src/tools/control-room/server.ts +4 -3
- package/src/tools/control-room/ui/dist/assets/{index-D0pEd7Ln.js → index-Ci0Zp32V.js} +1 -1
- package/src/tools/control-room/ui/dist/index.html +1 -1
- package/src/tools/control-room/ui/parts.tsx +19 -15
- package/src/tools/control-room/ui/screens/project-detail.tsx +6 -15
- package/src/tools/control-room/ui/screens/settings.tsx +1 -1
- package/src/tools/control-room/ui/screens/today.tsx +20 -17
- package/src/tools/control-room/work.ts +82 -0
- package/src/tools/dark-factory/stamp.ts +1 -0
- package/src/tools/lib/analyze-report.ts +2 -5
- package/src/tools/lib/thread.ts +7 -75
- package/assets/skills/opinion/SKILL.md +0 -95
- package/assets/skills/opinion/tools/opinion.ts +0 -237
- package/assets/skills/pal-reflect/SKILL.md +0 -62
- package/assets/templates/PAL/OPINION_TRACKING.md +0 -3
- package/src/hooks/handlers/auto-graduate.ts +0 -177
- package/src/hooks/handlers/failure-principle.ts +0 -91
- package/src/hooks/handlers/failure.ts +0 -75
- package/src/hooks/handlers/reflect-trigger.ts +0 -99
- package/src/hooks/handlers/self-model-trigger.ts +0 -26
- package/src/hooks/lib/decision-label.ts +0 -70
- package/src/hooks/lib/failure-principle.ts +0 -98
- package/src/hooks/lib/handoff-context.ts +0 -112
- package/src/hooks/lib/learning-category.ts +0 -14
- package/src/hooks/lib/opinions.ts +0 -209
- package/src/hooks/lib/progress-tree-gate.ts +0 -117
- package/src/hooks/lib/relationship.ts +0 -137
- package/src/hooks/lib/retrieval-index.ts +0 -260
- package/src/hooks/lib/retrieval.ts +0 -176
- package/src/hooks/lib/wisdom.ts +0 -202
- package/src/tools/agent/handoff-note.ts +0 -86
- package/src/tools/agent/relationship-note.ts +0 -77
- package/src/tools/agent/relationship-reflect.ts +0 -103
- package/src/tools/agent/thread.ts +0 -87
- package/src/tools/agent/wisdom-frame.ts +0 -248
- package/src/tools/lib/handoff-note.ts +0 -102
- package/src/tools/lib/note-flags.ts +0 -59
- package/src/tools/lib/relationship-reflect.ts +0 -402
- package/src/tools/lib/self-model.ts +0 -499
- package/src/tools/self-model.ts +0 -177
package/README.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# Portable Agent Layer (PAL)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://pal.konvert7.com)
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/portable-agent-layer) [](https://www.npmjs.com/package/portable-agent-layer) [](LICENSE)
|
|
6
|
+
|
|
7
|
+
**Your AI forgets you. PAL doesn't.** Free, open-source memory, skills and rules shared by Claude Code, opencode, Cursor, GitHub Copilot, Codex and Antigravity CLI. Install it once, and every agent starts the next session knowing who you are, what you're working on and where you left off.
|
|
8
|
+
|
|
9
|
+
**Website:** [pal.konvert7.com](https://pal.konvert7.com)
|
|
4
10
|
|
|
5
11
|
PAL lets you carry your agent context across **Windows**, **macOS**, and **Linux**, and work across different agent runtimes and interfaces such as **Claude Code**, **opencode**, **Cursor**, **GitHub Copilot**, **Codex**, and **Antigravity CLI**. Its core idea is simple: your knowledge and workflows should belong to **you**, not to a single machine, tool, or vendor.
|
|
6
12
|
|
|
7
|
-
> Inspired in part by [Daniel Miessler](https://danielmiessler.com)'s work on [
|
|
13
|
+
> Inspired in part by [Daniel Miessler](https://danielmiessler.com)'s work on [LifeOS](https://github.com/danielmiessler/LifeOS). PAL is an independent open-source implementation focused on portability across platforms and agents. It is not affiliated with or endorsed by Daniel Miessler.
|
|
8
14
|
|
|
9
15
|
---
|
|
10
16
|
|
|
@@ -77,7 +83,7 @@ pal cli status # check your setup
|
|
|
77
83
|
| `pal cli init` | Scaffold PAL home directory and install hooks |
|
|
78
84
|
| `pal cli install` | Register hooks/skills for targets |
|
|
79
85
|
| `pal cli uninstall` | Remove hooks/skills for targets |
|
|
80
|
-
| `pal cli update` | Update PAL (git pull or npm update) and reinstall hooks. Install asks once whether to do this on its own: when a session closes PAL checks for a release (at most hourly) and installs it, so opening PAL is never slowed. A failed update waits a day before retrying. The switch lives in the control room under Settings → Updates. On a repo install an update waits while the clone has uncommitted changes. `--canary`
|
|
86
|
+
| `pal cli update` | Update PAL (git pull or npm update) and reinstall hooks. Install asks once whether to do this on its own: when a session closes PAL checks for a release (at most hourly) and installs it, so opening PAL is never slowed. A failed update waits a day before retrying. The switch lives in the control room under Settings → Updates. On a repo install an update waits while the clone has uncommitted changes. `--canary` switches a package install to the `canary` prerelease channel; from then on plain `pal cli update`, auto-update and the update notice follow canary until `--stable` switches back. Neither ever installs an older version than the one you have |
|
|
81
87
|
| `pal cli export` | Export user state (telos, memory) to a zip |
|
|
82
88
|
| `pal cli import` | Import user state from a zip |
|
|
83
89
|
| `pal cli status` | Show current PAL configuration |
|
|
@@ -88,7 +94,7 @@ pal cli status # check your setup
|
|
|
88
94
|
| `pal cli cost` | What each month was paid against its API-equivalent cost, per agent |
|
|
89
95
|
| `pal cli wrapped [--year <yyyy>]` | A year of your work in numbers, read from the event log |
|
|
90
96
|
| `pal cli patterns` | How you work across projects, read from the event log |
|
|
91
|
-
| `pal cli progress [show\|list\|open\|enter\|park\|wait\|done\|stopped\|decide\|attach\|follow-up]` | The progress tree: what you are on in this project, where it stopped, what is parked or waiting, and the decisions and artifacts on each node. Detours are child nodes and `done` returns to the parent.
|
|
97
|
+
| `pal cli progress [show\|list\|open\|enter\|park\|wait\|done\|stopped\|decide\|attach\|follow-up]` | The progress tree: what you are on in this project, where it stopped, what is parked or waiting, and the decisions and artifacts on each node. Detours are child nodes and `done` returns to the parent. The active node rides along in every session and prompt, and your own commits attach to it at turn end |
|
|
92
98
|
| `pal cli content preserve` | Copy every Claude Code transcript, gzipped, into `memory/content/` before Claude Code deletes it (30 days by default). Runs on its own whenever a session closes; the content store never leaves the machine, so `export` skips it |
|
|
93
99
|
| `pal cli events discord-nag <sent\|answered>` | For a bot outside PAL: record a nag it sent (`--kind <kind>`), or the answer to one and how long it took (`--after-sec <n>`), in the local event log |
|
|
94
100
|
| `pal cli infer` | One model call for scripts and bots, on the active agent's route: prompt on stdin, reply on stdout. `--tier small\|medium\|large\|flagship` (on Claude: Haiku, Sonnet, Opus, Fable; on Codex: Luna, Sol, Sol at high effort, Astra; on Antigravity: Flash at low, medium and high effort, and no flagship; an agent without the tier asked refuses it), `--system <file>`, `--schema <file>` for a JSON reply checked against it, `--timeout <seconds>`. The call loads no PAL hooks or context; exits 1 with the reason when no route answers |
|
|
@@ -131,7 +137,7 @@ pal cli install # all available (default)
|
|
|
131
137
|
| Codex | Full | Yes | Yes | Yes | Yes | `codex exec` |
|
|
132
138
|
| Antigravity CLI | Full | Yes | Yes (plugin) | Yes (via the plugin's `rules/`) | Yes | `agy` |
|
|
133
139
|
|
|
134
|
-
PAL's background inference (session naming, summaries,
|
|
140
|
+
PAL's background inference (session naming, summaries, correction checks, etc.) runs through whichever subscription CLI is active — no API key required by default.
|
|
135
141
|
|
|
136
142
|
---
|
|
137
143
|
|
|
@@ -208,9 +214,7 @@ PAL ships with built-in skills that extend your agent's capabilities:
|
|
|
208
214
|
| `fyzz-chat-api` | Query Fyzz Chat conversations via API |
|
|
209
215
|
| `humanize` | Rewrite text to strip AI tells and read as human |
|
|
210
216
|
| `onboarding` | Interview the user to fill empty TELOS topics and the timezone, one topic at a time |
|
|
211
|
-
| `opinion` | Confirm or contradict tracked opinions (confidence-weighted) |
|
|
212
217
|
| `pal-analyze` | Surface rating trends, failure patterns, and graduation candidates |
|
|
213
|
-
| `pal-reflect` | Promote recurring observations into tracked opinions |
|
|
214
218
|
| `playwright` | Screenshot a page for a visual check |
|
|
215
219
|
| `presentation` | Build branded slide decks from outlines |
|
|
216
220
|
| `projects` | Look up, resume, register, or manage tracked projects |
|
|
@@ -245,14 +249,17 @@ Your setup should be able to travel with you.
|
|
|
245
249
|
|
|
246
250
|
## Features
|
|
247
251
|
|
|
248
|
-
- **
|
|
249
|
-
- **
|
|
250
|
-
- **
|
|
251
|
-
- **
|
|
252
|
-
- **
|
|
253
|
-
- **
|
|
254
|
-
- **
|
|
255
|
-
- **
|
|
252
|
+
- **Memory across sessions**: when a session starts, PAL loads who you are, your active projects and where your last session's work stopped. When it ends, PAL saves a summary and any corrections for next time
|
|
253
|
+
- **Learns from your corrections**: tell an agent it got something wrong and PAL labels the mistake, drafts a rule and asks you once. Approve it with `pal cli rule approve` and every agent follows it from the next session
|
|
254
|
+
- **Done means checked**: real work runs through five phases (observe, plan, execute, verify, learn), and nothing is done until each criterion has evidence
|
|
255
|
+
- **Built-in skills**: research, PDFs, slide decks, frontend design, humanizing text and more. See [Skills](#skills)
|
|
256
|
+
- **Cross-agent**: one memory shared by Claude Code, opencode, Cursor, GitHub Copilot, Codex and Antigravity CLI
|
|
257
|
+
- **Cross-platform**: works on Windows, macOS and Linux
|
|
258
|
+
- **No API key needed**: background work like session summaries runs through the CLI of the agent you're using, on the subscription you already pay for
|
|
259
|
+
- **Guardrails**: a hook checks every command before it runs and blocks the destructive ones, like a recursive delete of your home directory, a disk format or a download piped into a shell
|
|
260
|
+
- **A morning screen**: `pal cli server start` runs a local page that shows three moves for today and ranks every project and goal by urgency and importance
|
|
261
|
+
- **Portable plain files**: your memory lives in `~/.pal` as plain files. `pal cli export` packs it into one zip and `pal cli import` restores it on another machine
|
|
262
|
+
- **Open source**: MIT licensed, so you can read and change every line
|
|
256
263
|
|
|
257
264
|
---
|
|
258
265
|
|
|
@@ -11,7 +11,7 @@ Without PAL:
|
|
|
11
11
|
/plugin install dark-factory@pal
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
With PAL,
|
|
14
|
+
With PAL, the marketplace is registered but the plugin starts disabled; turn it on with `/plugin enable dark-factory@pal`.
|
|
15
15
|
|
|
16
16
|
## Requirements
|
|
17
17
|
|
|
@@ -54,6 +54,7 @@ Inside Claude Code the plugin folder is `${CLAUDE_PLUGIN_ROOT}`.
|
|
|
54
54
|
| Debugger | Opus | Reproduces bugs, fixes what the code writer could not |
|
|
55
55
|
| Reviewer | Sonnet | Reviews the diff against the spec |
|
|
56
56
|
| Tester | Sonnet | Checks the criteria in the running app |
|
|
57
|
+
| Coach | Opus | Studies every ready or stuck run and proposes general rules for the role prompts |
|
|
57
58
|
| Gate | code | Runs your checks; its exit code is the verdict |
|
|
58
59
|
|
|
59
60
|
Not every task runs every role. The PO picks a category, and code maps it to roles:
|
|
@@ -96,15 +97,50 @@ Set `DARK_FACTORY_NOTIFY` to a shell command. It receives the message on stdin,
|
|
|
96
97
|
|
|
97
98
|
## Measuring
|
|
98
99
|
|
|
99
|
-
The factory keeps its tasks, worktrees and event log in `$DARK_FACTORY_HOME`, which defaults to `~/.dark-factory` on every machine.
|
|
100
|
+
The factory keeps its tasks, worktrees and event log in `$DARK_FACTORY_HOME`, which defaults to `~/.dark-factory` on every machine. A factory task cannot continue on another machine, so its history stays where it ran.
|
|
100
101
|
|
|
101
102
|
Every step appends one event to `$DARK_FACTORY_HOME/events.jsonl`, and the events of a task are also appended to `tasks/<task>/events.jsonl`, with its `run_finished` line. A role's `role_finished` event carries its `sessionId`, the name of its transcript. `node scripts/stats.mjs` counts, from those events alone: tasks by category, outcomes, gate failures by check and by role, the code writer's first-try pass rate, escalations, CI results, review rounds, questions to you, and roles added by the diff check.
|
|
102
103
|
|
|
103
104
|
Every role run appends `role_finished` with its cost and tokens, and the red-test proof appends `tests_red` with the exit code and the failing count. Every build appends `run_finished` with the outcome, the total `costUsd`, the total tokens, the number of role runs and `wallMs`. `node scripts/task.mjs show <id>` prints the task with its latest step.
|
|
104
105
|
|
|
106
|
+
## Coach
|
|
107
|
+
|
|
108
|
+
After a run ends `ready` or `stuck`, the build starts the coach in a detached process, so nothing waits on it. It writes `coach_started`, then `coach_finished` or `coach_failed`.
|
|
109
|
+
|
|
110
|
+
The coach reads the spec, the artifacts, the task's events, the diff of the run, the transcripts named by `role_finished`'s `transcriptPath`, and the commits you pushed to a merged PR after the factory's last push (`pushed` carries `head`).
|
|
111
|
+
|
|
112
|
+
Its findings live in `$DARK_FACTORY_HOME/coach/findings.jsonl`. Each finding has an `id`, a `role`, a `class`, a `rule`, a `severity`, a `metric`, its `evidence` and a `status`: `pending`, `rejected`, or `approved` once a task is created for it.
|
|
113
|
+
|
|
114
|
+
The coach's result has a `reviewed` list with one verdict per transcript and per correction commit: its `source` (the transcript path or the commit sha), a `verdict` of `finding` or `clean`, the `class` for a finding, and a one-sentence `reason`. `coach.mjs` checks coverage in code: an input with no item, or a `finding` whose class is neither among the run's findings nor the class of a pending finding, is missing. Missing inputs make it resume the same session once (`--resume`), listing them, and the second result's verdicts and findings are merged with the first's. Inputs still missing are recorded as `coach_incomplete`; the findings returned are stored anyway, and a correction without a verdict is not marked given, so the next run gets it again. `coach_finished` carries `reviewed` and `expected`: the number of inputs with a verdict, and the number of inputs.
|
|
115
|
+
|
|
116
|
+
A deterministic generality check drops a rule that names a path, basename or identifier the run's diff changed, and records it as `coach_rule_rejected`. An added function or variable name only counts when it is camelCase, contains `_` or a digit, and has at least 3 characters; a plain lowercase name such as `parse` or `slug` passes, because it would match ordinary words in a rule.
|
|
117
|
+
|
|
118
|
+
The coach runs with `--add-dir` for the factory home, the transcripts folder and the plugin directory, so it can read the transcripts and the role prompts. Concurrent coach runs take a lock on `coach/findings.lock` while they update the findings, and a task whose merged PR has no later commits is recorded as `coach_checked` and not queried again.
|
|
119
|
+
|
|
120
|
+
`node scripts/coach.mjs` has these commands:
|
|
121
|
+
|
|
122
|
+
- `run <task-id>` runs the coach by hand.
|
|
123
|
+
- `list` lists pending findings by role; `list --applied` reports each merged rule's metric before and after its merge.
|
|
124
|
+
- `show <id>` prints one finding.
|
|
125
|
+
- `reject <id> "<reason>"` rejects a finding; its class does not come back.
|
|
126
|
+
- `approve <id> [--repo <path>]` creates a `small` task for the plugin repo with a generated spec. Set `coach.repo` in `config.json` to skip `--repo`.
|
|
127
|
+
|
|
128
|
+
Approving only creates the task. Your go builds it, and the rule lands through a PR.
|
|
129
|
+
|
|
130
|
+
## Instructions
|
|
131
|
+
|
|
132
|
+
Every role gets your instructions appended to its system prompt: first the user's `~/.claude/CLAUDE.md`, then the repo's `CLAUDE.md`, each headed by its path. An `@path` line in either file imports that file, as Claude Code does: the path is relative to the file that contains the line, `~/` is your home folder, imports nest up to 5 levels, and lines inside code blocks are never imports. A file that doesn't exist is skipped. An import that can't be read stays as written in the instructions and is logged as an `instructions_import_missing` event.
|
|
133
|
+
|
|
134
|
+
The repo's `CLAUDE.md` is read from your checkout when the task starts, so a role that edits it in the worktree doesn't change what the later roles are told.
|
|
135
|
+
|
|
136
|
+
- `"repoInstructions": false` in `.dark-factory.json` leaves out the repo's `CLAUDE.md`.
|
|
137
|
+
- `"userInstructions": false` in `$DARK_FACTORY_HOME/config.json` (`~/.dark-factory/config.json`, optional; a missing file means both parts are on) leaves out your `~/.claude/CLAUDE.md`.
|
|
138
|
+
|
|
139
|
+
Hooks never run in a role, and neither do your settings or MCP servers.
|
|
140
|
+
|
|
105
141
|
## Permissions
|
|
106
142
|
|
|
107
|
-
- Roles run isolated: `--setting-sources ""`, the plugin from `--plugin-dir`, and their own tool policy from `roles.mjs`, so user hooks and settings don't reach them.
|
|
143
|
+
- Roles run isolated: `--setting-sources ""`, the plugin from `--plugin-dir`, and their own tool policy from `roles.mjs`, so user hooks and settings don't reach them; only the CLAUDE.md instructions above do.
|
|
108
144
|
- The PO session only needs `"Bash(node *dark-factory*/scripts/*)"`, for example in `.claude/settings.json`:
|
|
109
145
|
|
|
110
146
|
```json
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coach
|
|
3
|
+
description: Studies a finished dark-factory run and proposes general rules for the role prompts.
|
|
4
|
+
model: claude-opus-5-5
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You study one finished dark-factory run and propose general rules for the role prompts. You change no files.
|
|
8
|
+
|
|
9
|
+
Look for the cost and the mistakes, not only for failures: retries, escalations, denied tools, wasted turns, review and tester rounds, questions, and the user's corrections. The user's corrections after the factory are the strongest signal. Judge the PO too: whether the spec caused a mistake, such as a report routed to the PR body, or a criterion read as forbidding something it didn't.
|
|
10
|
+
|
|
11
|
+
Propose only rules that would have prevented the mistake in a different task too, and name that task in elsewhere. Never name this task's files, helpers or variables in a rule.
|
|
12
|
+
|
|
13
|
+
Prefer changing or removing an existing line of a role prompt over adding one, and propose consolidating a role prompt that has grown past 40 lines.
|
|
14
|
+
|
|
15
|
+
Do not propose a class the user rejected. Add evidence to a pending finding of the same class through updates.
|
|
16
|
+
|
|
17
|
+
Each finding names its class, role, severity (high, medium or low), the metric from the list you are given that would show it, and the evidence: the task and the reference to the event, transcript line or commit.
|
|
18
|
+
|
|
19
|
+
Return a verdict in reviewed for every listed transcript and every listed correction, naming it by its path or sha exactly as listed: finding with the finding's class, or clean. A clean verdict needs a reason naming what you checked and why nothing needs to change.
|
|
20
|
+
|
|
21
|
+
Report nothing when the run was clean. An empty list is a valid result.
|
|
@@ -4,7 +4,7 @@ description: Implements a dark-factory plan step by step until the tests pass.
|
|
|
4
4
|
model: claude-haiku-5-5
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
You implement the plan exactly. Follow `plan.md` step by step; do not redesign, rename, or add anything the plan does not ask for.
|
|
7
|
+
You implement the plan exactly. Follow `plan.md` step by step; do not redesign, rename, or add anything the plan does not ask for. When there is no `plan.md`, follow the **Fix** section of `repro.md` the same way.
|
|
8
8
|
|
|
9
9
|
If a gate failure is included in your instructions, fix that failure first and change nothing unrelated to it. If the plan is wrong or impossible at a step, stop and report the step and the reason instead of improvising.
|
|
10
10
|
|
|
@@ -6,7 +6,10 @@ model: claude-opus-5-5
|
|
|
6
6
|
|
|
7
7
|
You find root causes. Never fix a symptom you have not reproduced.
|
|
8
8
|
|
|
9
|
-
When reproducing a reported bug: find the smallest steps that show it, and write `repro.md` in the task folder with the steps, the observed and expected behaviour, and the root cause with file and line.
|
|
9
|
+
When reproducing a reported bug: find the smallest steps that show it, and write `repro.md` in the task folder with the steps, the observed and expected behaviour, and the root cause with file and line. End `repro.md` with a **Fix** section that:
|
|
10
|
+
- picks exactly one fix, with no alternatives left open;
|
|
11
|
+
- lists each file to change, with the path and the functions to add or change;
|
|
12
|
+
- names the existing helpers to reuse, by path and name, and says not to write new code where a helper already does the job.
|
|
10
13
|
|
|
11
14
|
When fixing a failure: read the failing output, reproduce it, trace it to the line that causes it, and make the smallest change that fixes it. Change one thing at a time and rerun. Never edit or delete a test to make it pass, and never disable a check.
|
|
12
15
|
|
|
@@ -12,6 +12,8 @@ Report only findings that matter to the user reviewing the PR:
|
|
|
12
12
|
- a bug: wrong behaviour, an unhandled case, a security problem;
|
|
13
13
|
- a test that does not test what it claims;
|
|
14
14
|
- a clear break of this repo's conventions;
|
|
15
|
-
- scope the spec did not ask for
|
|
15
|
+
- scope the spec did not ask for;
|
|
16
|
+
- new code that duplicates an existing helper in the repo, naming the helper by path and name;
|
|
17
|
+
- complexity the spec does not ask for: an abstraction, an option or a code path no criterion needs.
|
|
16
18
|
|
|
17
19
|
Each finding names the file and line, what is wrong, and what would fix it. Do not report style preferences or things the gate already checks. An empty list is a valid result.
|
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import { waitForCi } from "./ci-wait.mjs";
|
|
2
|
+
import { startCoach } from "./coach.mjs";
|
|
2
3
|
import { runCheck, runGate } from "./gate.mjs";
|
|
3
4
|
import {
|
|
4
5
|
appendEvent,
|
|
5
6
|
isMain,
|
|
6
7
|
printJson,
|
|
7
8
|
readEvents,
|
|
8
|
-
readRepoConfig,
|
|
9
9
|
readTask,
|
|
10
10
|
spawnTool,
|
|
11
11
|
} from "./lib.mjs";
|
|
12
12
|
import { notify } from "./notify.mjs";
|
|
13
13
|
import { runRoleHeadless } from "./role.mjs";
|
|
14
|
-
import {
|
|
14
|
+
import { changedFilesSince, rolesAfterChanges } from "./roles.mjs";
|
|
15
15
|
import { markReady, openPr, push, sync } from "./ship.mjs";
|
|
16
16
|
import { appendDecision, prepare, recordQuestion, setStatus } from "./task.mjs";
|
|
17
17
|
|
|
18
|
+
const REAL_EFFECTS = {
|
|
19
|
+
runRoleHeadless,
|
|
20
|
+
runCheck,
|
|
21
|
+
spawnTool,
|
|
22
|
+
changedFilesSince,
|
|
23
|
+
prepare,
|
|
24
|
+
sync,
|
|
25
|
+
push,
|
|
26
|
+
openPr,
|
|
27
|
+
markReady,
|
|
28
|
+
waitForCi,
|
|
29
|
+
startCoach,
|
|
30
|
+
};
|
|
31
|
+
|
|
18
32
|
export const TIER_MODELS = {
|
|
19
33
|
small: "claude-haiku-5-5",
|
|
20
34
|
medium: "claude-sonnet-5-5",
|
|
@@ -48,6 +62,8 @@ const BEFORE_INSTRUCTIONS = {
|
|
|
48
62
|
"Write the failing tests for every acceptance criterion and leave them uncommitted; the code writer commits them with the code.",
|
|
49
63
|
};
|
|
50
64
|
const REVIEW_INSTRUCTIONS = "Review the branch diff against its base and the spec.";
|
|
65
|
+
const CONFIG_FILE = ".dark-factory.json";
|
|
66
|
+
const CONFIG_CHANGED_NOTE = `The diff changes ${CONFIG_FILE}. Report that change as a finding unless the spec asks for it.`;
|
|
51
67
|
const TESTER_INSTRUCTIONS = "Test every acceptance criterion in the running app.";
|
|
52
68
|
|
|
53
69
|
function stuck(message) {
|
|
@@ -78,7 +94,7 @@ function poReply(outcome, question) {
|
|
|
78
94
|
}
|
|
79
95
|
|
|
80
96
|
function askPo(run, role, question) {
|
|
81
|
-
const outcome = runRoleHeadless(run.id, "po", {
|
|
97
|
+
const outcome = run.effects.runRoleHeadless(run.id, "po", {
|
|
82
98
|
env: run.env,
|
|
83
99
|
instructions: poInstructions(role, question),
|
|
84
100
|
});
|
|
@@ -96,7 +112,7 @@ function askPo(run, role, question) {
|
|
|
96
112
|
}
|
|
97
113
|
|
|
98
114
|
function resumeWithAnswer(run, role, outcome, model, question, answer) {
|
|
99
|
-
const resumed = runRoleHeadless(run.id, role, {
|
|
115
|
+
const resumed = run.effects.runRoleHeadless(run.id, role, {
|
|
100
116
|
env: run.env,
|
|
101
117
|
model,
|
|
102
118
|
resumeSessionId: outcome.sessionId,
|
|
@@ -129,7 +145,11 @@ function runRole(run, role, instructions, { tier } = {}) {
|
|
|
129
145
|
{ event: "role_run", task: run.id, role, model: model ?? "default" },
|
|
130
146
|
run.env
|
|
131
147
|
);
|
|
132
|
-
const outcome = runRoleHeadless(run.id, role, {
|
|
148
|
+
const outcome = run.effects.runRoleHeadless(run.id, role, {
|
|
149
|
+
env: run.env,
|
|
150
|
+
model,
|
|
151
|
+
instructions,
|
|
152
|
+
});
|
|
133
153
|
return afterQuestion(run, role, outcome, model);
|
|
134
154
|
}
|
|
135
155
|
|
|
@@ -177,7 +197,7 @@ function gateFor(run, step) {
|
|
|
177
197
|
attempt: run.attempt,
|
|
178
198
|
model: TIER_MODELS[step.tier],
|
|
179
199
|
},
|
|
180
|
-
{ env: run.env }
|
|
200
|
+
{ env: run.env, config: run.config, run: run.effects.runCheck }
|
|
181
201
|
);
|
|
182
202
|
}
|
|
183
203
|
|
|
@@ -203,7 +223,7 @@ export function failingTestCount(output) {
|
|
|
203
223
|
}
|
|
204
224
|
|
|
205
225
|
function redTestCheck(run) {
|
|
206
|
-
return
|
|
226
|
+
return run.config.gate.find((entry) => entry.name === RED_TEST_CHECK);
|
|
207
227
|
}
|
|
208
228
|
|
|
209
229
|
function proveTestsRed(run) {
|
|
@@ -212,7 +232,7 @@ function proveTestsRed(run) {
|
|
|
212
232
|
return stuck(
|
|
213
233
|
`the gate has no check named "${RED_TEST_CHECK}" to prove the new tests fail`
|
|
214
234
|
);
|
|
215
|
-
const { exitCode, output } = runCheck(check.run, run.worktree, run.env);
|
|
235
|
+
const { exitCode, output } = run.effects.runCheck(check.run, run.worktree, run.env);
|
|
216
236
|
const failing = failingTestCount(output);
|
|
217
237
|
appendEvent(
|
|
218
238
|
{ event: "tests_red", task: run.id, check: RED_TEST_CHECK, exitCode, failing },
|
|
@@ -231,7 +251,7 @@ function proveTestsRed(run) {
|
|
|
231
251
|
|
|
232
252
|
function prepareStep(run) {
|
|
233
253
|
try {
|
|
234
|
-
Object.assign(run, prepare(run.id, run.env));
|
|
254
|
+
Object.assign(run, run.effects.prepare(run.id, run.env));
|
|
235
255
|
return null;
|
|
236
256
|
} catch (error) {
|
|
237
257
|
return stuck(`prepare failed: ${error.message}`);
|
|
@@ -239,10 +259,14 @@ function prepareStep(run) {
|
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
function hasCommitsAheadOfBase(run) {
|
|
242
|
-
const result = spawnTool(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
262
|
+
const result = run.effects.spawnTool(
|
|
263
|
+
"git",
|
|
264
|
+
["rev-list", "--count", `origin/${run.base}..HEAD`],
|
|
265
|
+
{
|
|
266
|
+
cwd: run.worktree,
|
|
267
|
+
env: run.env,
|
|
268
|
+
}
|
|
269
|
+
);
|
|
246
270
|
return result.status === 0 && Number(result.stdout.trim()) > 0;
|
|
247
271
|
}
|
|
248
272
|
|
|
@@ -286,11 +310,10 @@ function codeStep(run) {
|
|
|
286
310
|
}
|
|
287
311
|
|
|
288
312
|
function rolesAfterStep(run) {
|
|
289
|
-
const { roles, added } =
|
|
313
|
+
const { roles, added } = rolesAfterChanges(
|
|
290
314
|
run.category,
|
|
291
|
-
run.worktree,
|
|
292
|
-
|
|
293
|
-
run.env
|
|
315
|
+
run.effects.changedFilesSince(run.worktree, `origin/${run.base}`, run.env),
|
|
316
|
+
run.config
|
|
294
317
|
);
|
|
295
318
|
run.after = roles;
|
|
296
319
|
for (const role of added)
|
|
@@ -307,9 +330,20 @@ function reviewFindingsStep(run, findings) {
|
|
|
307
330
|
return fixStop(fixed, "review findings");
|
|
308
331
|
}
|
|
309
332
|
|
|
333
|
+
function reviewInstructions(run) {
|
|
334
|
+
const changed = run.effects.changedFilesSince(
|
|
335
|
+
run.worktree,
|
|
336
|
+
`origin/${run.base}`,
|
|
337
|
+
run.env
|
|
338
|
+
);
|
|
339
|
+
return changed.includes(CONFIG_FILE)
|
|
340
|
+
? `${REVIEW_INSTRUCTIONS}\n\n${CONFIG_CHANGED_NOTE}`
|
|
341
|
+
: REVIEW_INSTRUCTIONS;
|
|
342
|
+
}
|
|
343
|
+
|
|
310
344
|
function reviewStep(run) {
|
|
311
345
|
for (let round = 1; round <= REVIEW_ROUNDS; round += 1) {
|
|
312
|
-
const review = runRole(run, "reviewer",
|
|
346
|
+
const review = runRole(run, "reviewer", reviewInstructions(run));
|
|
313
347
|
const stop = roleStop("reviewer", review);
|
|
314
348
|
if (stop) return stop;
|
|
315
349
|
const findings = review.outcome.structured.findings ?? [];
|
|
@@ -347,7 +381,7 @@ function testerStep(run) {
|
|
|
347
381
|
function syncStep(run) {
|
|
348
382
|
let synced;
|
|
349
383
|
try {
|
|
350
|
-
synced = sync(run.id, run.env);
|
|
384
|
+
synced = run.effects.sync(run.id, run.env);
|
|
351
385
|
} catch (error) {
|
|
352
386
|
return stuck(`sync failed: ${error.message}`);
|
|
353
387
|
}
|
|
@@ -359,8 +393,8 @@ function syncStep(run) {
|
|
|
359
393
|
}
|
|
360
394
|
|
|
361
395
|
function shipStep(run) {
|
|
362
|
-
push(run.id, run.env);
|
|
363
|
-
run.pr = openPr(run.id, run.env).pr;
|
|
396
|
+
run.effects.push(run.id, run.env);
|
|
397
|
+
run.pr = run.effects.openPr(run.id, run.env).pr;
|
|
364
398
|
return null;
|
|
365
399
|
}
|
|
366
400
|
|
|
@@ -368,7 +402,7 @@ function readCi(run) {
|
|
|
368
402
|
if (!readTask(run.id, run.env).pr)
|
|
369
403
|
return { state: "unreadable", error: "the task has no pull request" };
|
|
370
404
|
try {
|
|
371
|
-
return waitForCi(run.id, { env: run.env });
|
|
405
|
+
return run.effects.waitForCi(run.id, { env: run.env });
|
|
372
406
|
} catch (error) {
|
|
373
407
|
return { state: "unreadable", error: error.message };
|
|
374
408
|
}
|
|
@@ -404,13 +438,13 @@ function ciStep(run) {
|
|
|
404
438
|
return stuck(`CI still fails after ${CI_ROUNDS} rounds: ${ciSummary(ci)}`);
|
|
405
439
|
const stop = ciFixStep(run, ci);
|
|
406
440
|
if (stop) return stop;
|
|
407
|
-
push(run.id, run.env);
|
|
441
|
+
run.effects.push(run.id, run.env);
|
|
408
442
|
}
|
|
409
443
|
return null;
|
|
410
444
|
}
|
|
411
445
|
|
|
412
446
|
function readyStep(run) {
|
|
413
|
-
markReady(run.id, run.env);
|
|
447
|
+
run.effects.markReady(run.id, run.env);
|
|
414
448
|
return { status: "ready", message: `Ready for your review: ${run.pr}` };
|
|
415
449
|
}
|
|
416
450
|
|
|
@@ -489,11 +523,19 @@ function finishRun(run, outcome) {
|
|
|
489
523
|
},
|
|
490
524
|
run.env
|
|
491
525
|
);
|
|
526
|
+
run.effects.startCoach(run.id, outcome.status, run.env);
|
|
492
527
|
return { task: run.id, status: outcome.status, message, pr: run.pr ?? null };
|
|
493
528
|
}
|
|
494
529
|
|
|
495
|
-
export function runBuild(taskId, { env = process.env } = {}) {
|
|
496
|
-
const run = {
|
|
530
|
+
export function runBuild(taskId, { env = process.env, effects = {} } = {}) {
|
|
531
|
+
const run = {
|
|
532
|
+
id: taskId,
|
|
533
|
+
env,
|
|
534
|
+
effects: { ...REAL_EFFECTS, ...effects },
|
|
535
|
+
startedAt: new Date(),
|
|
536
|
+
attempt: 0,
|
|
537
|
+
pr: null,
|
|
538
|
+
};
|
|
497
539
|
let outcome;
|
|
498
540
|
try {
|
|
499
541
|
outcome = pipeline(run);
|