done-done-axi 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/README.md +10 -7
- package/dist/cli.js +158 -10
- package/docs/reference.md +24 -3
- package/integrations/firstmate/brief-include.md +4 -3
- package/integrations/firstmate/merge-gate +7 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,13 +94,13 @@ npm install -g --prefix ~/.local --allow-git=all github:umeranjum17/done-done-ax
|
|
|
94
94
|
|
|
95
95
|
To skip the install, run `npx -y --allow-git=all github:umeranjum17/done-done-axi` wherever this README says `done-done`.
|
|
96
96
|
|
|
97
|
-
Once
|
|
97
|
+
Once done-done-axi is published to npm, the registry line takes over as the install:
|
|
98
98
|
|
|
99
99
|
```sh
|
|
100
100
|
npm install -g --prefix ~/.local done-done-axi
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
<!-- TODO(dd-npm-name): when done-done-axi
|
|
103
|
+
<!-- TODO(dd-npm-name): when done-done-axi is published to npm, flip the primary back to the registry line above and make `npx -y done-done-axi` the skip-the-install path. -->
|
|
104
104
|
|
|
105
105
|
**2. Set it up in your app's repo**
|
|
106
106
|
|
|
@@ -109,7 +109,7 @@ cd your-app
|
|
|
109
109
|
done-done init
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
`init` writes a minimal `.done-done.yaml` (only if you don't already have one) that names your app.
|
|
112
|
+
`init` writes a minimal `.done-done.yaml` (only if you don't already have one) that names your app. A plain `init` never overwrites an existing config. Evidence lives outside the repo, in `~/.done-done/evidence/<app>`, so it outlives the checkout that captured it. Then describe how to capture proof — swap the command for whatever drives your real app and saves screenshots, recordings or logs:
|
|
113
113
|
|
|
114
114
|
```yaml
|
|
115
115
|
journeys:
|
|
@@ -119,15 +119,18 @@ journeys:
|
|
|
119
119
|
|
|
120
120
|
The command runs from the repo root with `DONE_DONE_EVIDENCE_ROOT` (a new folder for this run in the evidence root) and `DONE_DONE_RUN_ID` set, and `${RUN_ID}` in it is replaced. A run that writes nothing into its folder fails.
|
|
121
121
|
|
|
122
|
-
Optional: `done-done init --agent claude` adds a Claude Code SessionStart hook to the repo's `.claude/settings.json` that shows each session the bare `done-done` state. See the [reference](docs/reference.md#set-up-a-repo).
|
|
122
|
+
Optional: `done-done init --agent claude` adds a Claude Code SessionStart hook to the repo's `.claude/settings.json` that shows each session the bare `done-done` state, `done-done init --agent codex` adds the same SessionStart hook to the repo's `.codex/hooks.json` for Codex, and `done-done init --agent opencode` adds a plugin to the repo's `.opencode/plugins/` that adds the state to the first message of each opencode session. See the [reference](docs/reference.md#set-up-a-repo).
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
Record the recipe in the repo so anyone can re-run the proof, then commit it (and `.claude/settings.json`, `.codex/hooks.json`, or `.opencode/plugins/done-done.js` if you added a hook):
|
|
125
125
|
|
|
126
126
|
```sh
|
|
127
|
+
done-done init --journey smoke -- 'npm test > $DONE_DONE_EVIDENCE_ROOT/${RUN_ID}.log'
|
|
127
128
|
git add .done-done.yaml
|
|
128
129
|
git commit -m "Add done-done"
|
|
129
130
|
```
|
|
130
131
|
|
|
132
|
+
A `run -- <command>` whose command matches no recorded journey still runs, but its result hints that the recipe is not in the repo.
|
|
133
|
+
|
|
131
134
|
**3. Run it, then check it**
|
|
132
135
|
|
|
133
136
|
```sh
|
|
@@ -173,7 +176,7 @@ cp "$(npm root -g)/done-done-axi/integrations/firstmate/merge-gate" "$FM_HOME/co
|
|
|
173
176
|
```
|
|
174
177
|
|
|
175
178
|
- **`brief-include.md`**: Firstmate adds `config/brief-include.md` to every worker brief. It tells the worker to set up done-done, run a journey on its committed change, write a `## Checks` block, pass `check`, and record a `receipt`. The evidence and receipt stay in `~/.done-done/evidence/<app>` after teardown deletes the worktree.
|
|
176
|
-
- **`merge-gate`** (optional): `merge-gate <task-id> <worktree
|
|
179
|
+
- **`merge-gate`** (optional): `merge-gate <task-id> <worktree|-> <head> <pr-url|->`. In the worktree, it refuses a PR whose head is not `<head>`, then runs `done-done check --from <PR URL or $FM_HOME/data/<task-id>/checks.md> --trusted-ref <default branch>` (`origin/HEAD`, else `origin/main` or `origin/master`, else the local `main` or `master` in a repo with no remote), then `done-done receipt --verify <head>`. Both paths read done-done's rules from your default branch, never from the task's own branch, so merge `.done-done.yaml` there before you rely on it. The gate does not fetch: it reads the local `origin` refs, so run `git fetch origin` before you run it. It needs `done-done` on PATH (the `npm install` line above). A nonzero exit refuses the merge, and done-done's output says why. A worktree of `-` means the task recorded none: the gate does not cd and runs git and done-done in the current directory (the caller's project clone). Upstream Firstmate does not call a merge gate yet, so this file has no effect until it does; you can run it by hand before a merge.
|
|
177
180
|
|
|
178
181
|
Another fleet works the same way: add the include text to whatever your workers read first, and run `merge-gate` before each merge, with `FM_HOME` set to the folder that holds `data/<task-id>/checks.md` for local-only tasks.
|
|
179
182
|
|
|
@@ -184,7 +187,7 @@ Another fleet works the same way: add the include text to whatever your workers
|
|
|
184
187
|
|
|
185
188
|
## What's next
|
|
186
189
|
|
|
187
|
-
So far: `init`, `run`, `check`, `receipt`, the opt-in screenshot judge and the opt-in Claude Code session
|
|
190
|
+
So far: `init`, `run`, `check`, `receipt`, the opt-in screenshot judge and the opt-in Claude Code, Codex and opencode session hooks. Coming next: help writing the proof steps for your app.
|
|
188
191
|
|
|
189
192
|
## Develop
|
|
190
193
|
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import { dirname, isAbsolute, join, matchesGlob, relative, resolve, sep } from 'node:path';
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import { encode } from '@toon-format/toon';
|
|
9
|
+
import { Document, parseDocument } from 'yaml';
|
|
9
10
|
import { loadConfig, UsageError } from './config.js';
|
|
10
11
|
import { judge } from './judge.js';
|
|
11
12
|
import { checkMedia } from './media.js';
|
|
@@ -24,6 +25,7 @@ const CHECKS_TEXT = `Checks block (--from): the newest "## Checks" heading, then
|
|
|
24
25
|
// One table drives `--help` and every `<command> --help`: each command's usage lines and the flags it takes.
|
|
25
26
|
const FLAGS = {
|
|
26
27
|
'-- <command...>': 'run this command instead of a journey; ${RUN_ID} in it becomes the run id',
|
|
28
|
+
'--journey <name>': 'record this command as a journey in .done-done.yaml; ${RUN_ID} in it becomes the run id',
|
|
27
29
|
'--open': 'open a run for a hand- or agent-driven proof; prints its id and folder',
|
|
28
30
|
'--close <run-id>': 'seal an open run and record the files in its folder',
|
|
29
31
|
'--outcome "<claim>"': 'the outcome the evidence files prove; the files follow as arguments',
|
|
@@ -32,7 +34,7 @@ const FLAGS = {
|
|
|
32
34
|
'--claim-id <id>': 'the claim the evidence proves; evidence already used for another claim id is reuse (default: the current branch, or the PR branch with --from <PR URL>; required on a detached HEAD)',
|
|
33
35
|
'--judge': 'a model says whether each PNG/JPEG capture shows the outcome (off by default; or judge.enabled: true)',
|
|
34
36
|
'--verify <head>': 'read-only: verify the receipt recorded for <head>',
|
|
35
|
-
'--agent
|
|
37
|
+
'--agent <agent>': 'also add a SessionStart hook to .claude/settings.json (claude) or .codex/hooks.json (codex) that shows `done-done` to each session, or a plugin to .opencode/plugins that adds it to the first message of each opencode session (opt-in)',
|
|
36
38
|
};
|
|
37
39
|
// `more` is the reference text a command needs beyond its flags, shared with the top-level help.
|
|
38
40
|
const COMMANDS = {
|
|
@@ -40,8 +42,11 @@ const COMMANDS = {
|
|
|
40
42
|
usage: [
|
|
41
43
|
['done-done init', 'write a minimal .done-done.yaml (evidence lives outside the repo)'],
|
|
42
44
|
['done-done init --agent claude', 'also show this repo\'s done-done state at each Claude Code session start'],
|
|
45
|
+
['done-done init --agent codex', 'also show this repo\'s done-done state at each Codex session start'],
|
|
46
|
+
['done-done init --agent opencode', 'also add this repo\'s done-done state to the first message of each opencode session'],
|
|
47
|
+
['done-done init --journey <name> -- <command...>', "record a journey's capture recipe in .done-done.yaml"],
|
|
43
48
|
],
|
|
44
|
-
flags: ['--agent
|
|
49
|
+
flags: ['--agent <agent>', '--journey <name>', '-- <command...>'],
|
|
45
50
|
more: [JOURNEYS_TEXT],
|
|
46
51
|
},
|
|
47
52
|
run: {
|
|
@@ -214,6 +219,14 @@ function closeRun(repo, cfg, rec) {
|
|
|
214
219
|
result.help = [reason
|
|
215
220
|
? `Fix the capture, commit, then re-run: ${captureHint(cfg)}`
|
|
216
221
|
: `Check this proof: done-done check --outcome "<what you proved>" ${rel(rec.files[0].path)}`];
|
|
222
|
+
// A command that matches no repo-recorded journey cannot be re-run from the repo recipe.
|
|
223
|
+
// A journey run expands ${RUN_ID} to the run id, so compare with the placeholder restored.
|
|
224
|
+
if (rec.command) {
|
|
225
|
+
const template = rec.command.replaceAll(rec.run_id, '${RUN_ID}');
|
|
226
|
+
const recorded = Object.values(cfg.journeys).some((j) => j.command === template);
|
|
227
|
+
if (!recorded)
|
|
228
|
+
result.help.push(`this command's recipe is not in the repo (.done-done.yaml journeys); record it with: done-done init --journey <name> -- <command>`);
|
|
229
|
+
}
|
|
217
230
|
emit(result);
|
|
218
231
|
return reason ? FAIL : PASS;
|
|
219
232
|
}
|
|
@@ -689,9 +702,13 @@ function ensureIgnored(repo, rel) {
|
|
|
689
702
|
appendFileSync(file, `${text && !text.endsWith('\n') ? '\n' : ''}${entry}\n`);
|
|
690
703
|
return true;
|
|
691
704
|
}
|
|
692
|
-
// The
|
|
705
|
+
// The session-start hook `init --agent <agent>` adds; the agent shows its stdout to the session.
|
|
693
706
|
// Without done-done on PATH it prints nothing and exits 0, so it never breaks a session.
|
|
694
|
-
|
|
707
|
+
// Claude Code reads it from the repo's .claude/settings.json, Codex from the repo's .codex/hooks.json
|
|
708
|
+
// (project scope; Codex docs: <repo>/.codex/hooks.json, SessionStart, matcher omitted matches every start).
|
|
709
|
+
const HOOK_COMMAND = 'command -v done-done >/dev/null && done-done || true';
|
|
710
|
+
const CLAUDE_HOOK = HOOK_COMMAND;
|
|
711
|
+
const CODEX_HOOK = HOOK_COMMAND;
|
|
695
712
|
// The repo's .claude/settings.json with the hook merged in, keeping every other key and hook; no text when it is already there.
|
|
696
713
|
function claudeSettings(repo) {
|
|
697
714
|
const file = join(repo, '.claude', 'settings.json');
|
|
@@ -718,15 +735,135 @@ function claudeSettings(repo) {
|
|
|
718
735
|
settings.hooks = { ...settings.hooks, SessionStart: [...start, { hooks: [{ type: 'command', command: CLAUDE_HOOK }] }] };
|
|
719
736
|
return { file, text: `${JSON.stringify(settings, null, 2)}\n` };
|
|
720
737
|
}
|
|
738
|
+
// The repo's .codex/hooks.json with the hook merged in, keeping every other key and hook; no text when it is already there.
|
|
739
|
+
function codexSettings(repo) {
|
|
740
|
+
const file = join(repo, '.codex', 'hooks.json');
|
|
741
|
+
const bad = (why) => new UsageError(`.codex/hooks.json ${why}`, 'Fix .codex/hooks.json, then re-run: done-done init --agent codex');
|
|
742
|
+
const isObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
743
|
+
let settings = {};
|
|
744
|
+
if (existsSync(file)) {
|
|
745
|
+
try {
|
|
746
|
+
settings = JSON.parse(readFileSync(file, 'utf8'));
|
|
747
|
+
}
|
|
748
|
+
catch (e) {
|
|
749
|
+
throw bad(`is not valid JSON (${e.message})`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (!isObject(settings))
|
|
753
|
+
throw bad('is not a JSON object');
|
|
754
|
+
if (settings.hooks !== undefined && !isObject(settings.hooks))
|
|
755
|
+
throw bad('has "hooks" that is not an object');
|
|
756
|
+
const start = settings.hooks?.SessionStart ?? [];
|
|
757
|
+
if (!Array.isArray(start))
|
|
758
|
+
throw bad('has "hooks.SessionStart" that is not a list');
|
|
759
|
+
if (start.some((m) => Array.isArray(m?.hooks) && m.hooks.some((h) => h?.command === CODEX_HOOK)))
|
|
760
|
+
return { file };
|
|
761
|
+
settings.hooks = { ...settings.hooks, SessionStart: [...start, { hooks: [{ type: 'command', command: CODEX_HOOK }] }] };
|
|
762
|
+
return { file, text: `${JSON.stringify(settings, null, 2)}\n` };
|
|
763
|
+
}
|
|
764
|
+
// The opencode session plugin `init --agent opencode` adds: a project-scope file in
|
|
765
|
+
// .opencode/plugins/ (auto-loaded; the home config is never touched) whose chat.message
|
|
766
|
+
// hook runs the session command below on the first message of each session and appends
|
|
767
|
+
// the bare `done-done` state to the message parts. The appended part carries the prt_ id
|
|
768
|
+
// plus the session id from the hook input and the message id from the hook output (opencode
|
|
769
|
+
// passes no messageID to the hook), because opencode validates every
|
|
770
|
+
// part before save and drops the whole first message when one is invalid. Without
|
|
771
|
+
// done-done on PATH it appends nothing and never throws, so it never breaks a session.
|
|
772
|
+
function opencodePlugin(repo) {
|
|
773
|
+
const file = join(repo, '.opencode', 'plugins', 'done-done.js');
|
|
774
|
+
const text = `// Written by \`done-done init --agent opencode\`: shows this repo's bare \`done-done\`
|
|
775
|
+
// state to each opencode session. On the first message of a session the chat.message
|
|
776
|
+
// hook runs \`${CLAUDE_HOOK}\` and appends its output to the message parts.
|
|
777
|
+
// Without done-done on PATH it appends nothing and never throws, so it never breaks
|
|
778
|
+
// a session. Other files in .opencode/plugins/ are untouched; delete this file to opt out.
|
|
779
|
+
import { execFile } from "node:child_process";
|
|
780
|
+
import { randomUUID } from "node:crypto";
|
|
781
|
+
|
|
782
|
+
const COMMAND = "${CLAUDE_HOOK}";
|
|
783
|
+
|
|
784
|
+
const seen = new Set();
|
|
785
|
+
const firstMessage = (sessionID) => {
|
|
786
|
+
if (seen.has(sessionID)) return false;
|
|
787
|
+
seen.add(sessionID);
|
|
788
|
+
return true;
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
const run = (cwd) => new Promise((resolve) => {
|
|
792
|
+
execFile("sh", ["-c", COMMAND], { cwd, encoding: "utf8", timeout: 30000 }, (error, stdout) => resolve(error ? "" : stdout.trim()));
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
export const DoneDonePlugin = async ({ directory }) => {
|
|
796
|
+
return {
|
|
797
|
+
"chat.message": async (input, output) => {
|
|
798
|
+
if (!firstMessage(input.sessionID)) return;
|
|
799
|
+
const state = await run(directory);
|
|
800
|
+
if (state) output.parts.push({ id: \`prt_\${randomUUID()}\`, messageID: output.message.id, sessionID: input.sessionID, type: "text", text: state });
|
|
801
|
+
},
|
|
802
|
+
};
|
|
803
|
+
};
|
|
804
|
+
`;
|
|
805
|
+
if (existsSync(file)) {
|
|
806
|
+
if (readFileSync(file, 'utf8') === text)
|
|
807
|
+
return { file };
|
|
808
|
+
throw new UsageError('.opencode/plugins/done-done.js already exists with other content, so init will not overwrite it', 'Delete .opencode/plugins/done-done.js, then re-run: done-done init --agent opencode');
|
|
809
|
+
}
|
|
810
|
+
return { file, text };
|
|
811
|
+
}
|
|
721
812
|
function cmdInit(argv) {
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
813
|
+
const dash = argv.indexOf('--');
|
|
814
|
+
const { values } = parseArgs({
|
|
815
|
+
args: dash < 0 ? argv : argv.slice(0, dash),
|
|
816
|
+
options: { agent: { type: 'string' }, journey: { type: 'string' } },
|
|
817
|
+
allowPositionals: false,
|
|
818
|
+
});
|
|
819
|
+
const parts = dash < 0 ? [] : argv.slice(dash + 1);
|
|
820
|
+
const journey = values.journey ?? null;
|
|
821
|
+
if (values.agent !== undefined && values.agent !== 'claude' && values.agent !== 'codex' && values.agent !== 'opencode')
|
|
822
|
+
throw new UsageError(`--agent ${values.agent} is not supported; the supported values are claude, codex, opencode`, 'Run: done-done init --agent <claude|codex|opencode>');
|
|
823
|
+
if (journey === null && parts.length)
|
|
824
|
+
throw new UsageError('init needs --journey <name> to record a command after --');
|
|
825
|
+
if (journey !== null && !parts.some((p) => p.trim()))
|
|
826
|
+
throw new UsageError(`init --journey ${journey} needs a command after --`);
|
|
827
|
+
if (journey !== null && !/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(journey))
|
|
828
|
+
throw new UsageError(`invalid journey name ${journey}; use letters, digits, - or _`);
|
|
725
829
|
const repo = repoRoot();
|
|
726
830
|
const file = join(repo, '.done-done.yaml');
|
|
727
831
|
const existed = existsSync(file);
|
|
728
832
|
const cfg = loadConfig(repo); // validates the existing project config; malformed -> exit 2, no writes
|
|
729
|
-
const hook = values.agent ? claudeSettings(repo) : undefined; // likewise validated before any write
|
|
833
|
+
const hook = values.agent === 'claude' ? claudeSettings(repo) : values.agent === 'codex' ? codexSettings(repo) : values.agent === 'opencode' ? opencodePlugin(repo) : undefined; // likewise validated before any write
|
|
834
|
+
if (journey !== null) {
|
|
835
|
+
// Record the capture recipe in the repo so any checkout can re-run the proof.
|
|
836
|
+
// The command is stored verbatim; ${RUN_ID} in it becomes the run id, like a run -- command.
|
|
837
|
+
const command = parts.join(' ');
|
|
838
|
+
const doc = existed ? parseDocument(readFileSync(file, 'utf8')) : new Document({ app: cfg.app });
|
|
839
|
+
const top = (doc.toJS() ?? {});
|
|
840
|
+
if (typeof top !== 'object' || Array.isArray(top))
|
|
841
|
+
throw new UsageError(`${file}: top level must be a mapping`);
|
|
842
|
+
const journeys = top.journeys ?? {};
|
|
843
|
+
if (typeof journeys !== 'object' || Array.isArray(journeys))
|
|
844
|
+
throw new UsageError(`${file}: journeys must be a mapping`);
|
|
845
|
+
if (top.journeys == null)
|
|
846
|
+
doc.set('journeys', doc.createNode({}));
|
|
847
|
+
doc.setIn(['journeys', journey, 'command'], command);
|
|
848
|
+
writeFileSync(`${file}.tmp`, doc.toString({ lineWidth: 0 }));
|
|
849
|
+
renameSync(`${file}.tmp`, file);
|
|
850
|
+
const fresh = loadConfig(repo);
|
|
851
|
+
const rel = under(repo, fresh.evidence.root);
|
|
852
|
+
const added = !!rel && ensureIgnored(repo, rel);
|
|
853
|
+
const root = shown(repo, fresh.evidence.root);
|
|
854
|
+
const check = 'done-done check --outcome "<what you proved>" <files it recorded>';
|
|
855
|
+
emit({
|
|
856
|
+
command: 'init',
|
|
857
|
+
result: 'pass',
|
|
858
|
+
config: existed ? 'updated' : 'written',
|
|
859
|
+
journey,
|
|
860
|
+
app: fresh.app,
|
|
861
|
+
evidence_root: root,
|
|
862
|
+
gitignore: added ? `added ${rel.replace(/\/+$/, '')}/` : 'unchanged',
|
|
863
|
+
help: [`Capture proof: done-done run ${journey}, then: ${check}`],
|
|
864
|
+
});
|
|
865
|
+
return PASS;
|
|
866
|
+
}
|
|
730
867
|
if (!existed) {
|
|
731
868
|
// No evidence.root: proof goes to <local_root>/<app>, outside the repo, so it outlives this checkout.
|
|
732
869
|
// The pinned app name is what lets every checkout of the repo find the same evidence and receipts.
|
|
@@ -751,12 +888,23 @@ function cmdInit(argv) {
|
|
|
751
888
|
app: cfg.app,
|
|
752
889
|
evidence_root: root,
|
|
753
890
|
gitignore: added ? `added ${rel.replace(/\/+$/, '')}/` : 'unchanged',
|
|
754
|
-
...(hook &&
|
|
891
|
+
...(hook &&
|
|
892
|
+
(values.agent === 'codex'
|
|
893
|
+
? { codex_hook: hook.text ? 'added to .codex/hooks.json' : 'unchanged' }
|
|
894
|
+
: values.agent === 'opencode'
|
|
895
|
+
? { opencode_plugin: hook.text ? 'added to .opencode/plugins/done-done.js' : 'unchanged' }
|
|
896
|
+
: { claude_hook: hook.text ? 'added to .claude/settings.json' : 'unchanged' })),
|
|
755
897
|
help: [
|
|
756
898
|
...(Object.keys(cfg.journeys).length
|
|
757
899
|
? [`Capture proof: ${captureHint(cfg)}, then: ${check}`]
|
|
758
900
|
: [`Add a journey that drives the real app and saves proof under $DONE_DONE_EVIDENCE_ROOT (each run's own folder in ${root}), e.g. ${journeyExample}`, `Then commit, run \`done-done run smoke\`, and: ${check}`]),
|
|
759
|
-
...(hook?.text
|
|
901
|
+
...(hook?.text
|
|
902
|
+
? values.agent === 'codex'
|
|
903
|
+
? ['Commit .codex/hooks.json: each Codex session in this repo then starts with `done-done` output (it needs done-done on PATH)']
|
|
904
|
+
: values.agent === 'opencode'
|
|
905
|
+
? ['Commit .opencode/plugins/done-done.js: the first message of each opencode session in this repo then gets `done-done` output added (it needs done-done on PATH)']
|
|
906
|
+
: ['Commit .claude/settings.json: each Claude Code session in this repo then starts with `done-done` output (it needs done-done on PATH)']
|
|
907
|
+
: []),
|
|
760
908
|
],
|
|
761
909
|
});
|
|
762
910
|
return PASS;
|
package/docs/reference.md
CHANGED
|
@@ -14,13 +14,33 @@ For the install steps, see the [README](../README.md#use-it-from-your-agent). do
|
|
|
14
14
|
done-done init
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
`init` writes a minimal `.done-done.yaml` only when one does not exist: just `app`, the repo directory name, so every checkout of the repo finds the same evidence (see [Where proof lives](#where-proof-lives)). It sets no `evidence.root`, so evidence goes outside the repo. When an existing config puts `evidence.root` inside the repo (an older `root: .proof`), `init` adds it to `.gitignore` when git does not already ignore it.
|
|
17
|
+
`init` writes a minimal `.done-done.yaml` only when one does not exist: just `app`, the repo directory name, so every checkout of the repo finds the same evidence (see [Where proof lives](#where-proof-lives)). It sets no `evidence.root`, so evidence goes outside the repo. When an existing config puts `evidence.root` inside the repo (an older `root: .proof`), `init` adds it to `.gitignore` when git does not already ignore it. A plain `init` never rewrites the config: re-running is a no-op and a customized `.done-done.yaml` is left byte-for-byte; `.gitignore` gains a line only when the ignore entry is missing. A malformed `.done-done.yaml` makes `init` exit 2 with the file named and untouched. Its result says whether the config was `written` or left `unchanged`, the evidence root, what it added to `.gitignore`, and the next step.
|
|
18
|
+
|
|
19
|
+
Record a journey's capture recipe in the repo so any checkout can re-run the proof:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
done-done init --journey smoke -- 'echo added: milk > $DONE_DONE_EVIDENCE_ROOT/${RUN_ID}.log'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`init --journey <name> -- <command>` writes `journeys.<name>.command` into `.done-done.yaml` (creating the file with the pinned `app` when missing) and reports `written` or `updated` with the journey name. `${RUN_ID}` in the command becomes the run id, exactly as in `run`. Commit the config: proof only counts from a clean tree.
|
|
18
26
|
|
|
19
27
|
```sh
|
|
20
28
|
done-done init --agent claude
|
|
21
29
|
```
|
|
22
30
|
|
|
23
|
-
`--agent claude` also adds a Claude Code SessionStart hook to the repo's `.claude/settings.json` (project scope; your `~/.claude` is never touched), so each session starts with the bare `done-done` state as context. The hook runs `command -v done-done >/dev/null && done-done || true`: without done-done on PATH it prints nothing and exits 0, so it never breaks a session. An existing settings file keeps every other key and hook; re-running adds no duplicate. The result's `claude_hook` says `added to .claude/settings.json` or `unchanged`. Any other `--agent` value exits 2 naming the supported
|
|
31
|
+
`--agent claude` also adds a Claude Code SessionStart hook to the repo's `.claude/settings.json` (project scope; your `~/.claude` is never touched), so each session starts with the bare `done-done` state as context. The hook runs `command -v done-done >/dev/null && done-done || true`: without done-done on PATH it prints nothing and exits 0, so it never breaks a session. An existing settings file keeps every other key and hook; re-running adds no duplicate. The result's `claude_hook` says `added to .claude/settings.json` or `unchanged`. Any other `--agent` value exits 2 naming the supported values, and a malformed `.claude/settings.json` exits 2 untouched. Commit `.claude/settings.json` to share the hook.
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
done-done init --agent codex
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`--agent codex` also adds a Codex SessionStart hook to the repo's `.codex/hooks.json` (project scope; your `~/.codex` is never touched), so each session starts with the bare `done-done` state as context. Same contract as the Claude hook: it runs `command -v done-done >/dev/null && done-done || true`, keeps every other key and hook, adds no duplicate on re-run, and the result's `codex_hook` says `added to .codex/hooks.json` or `unchanged`. A malformed `.codex/hooks.json` exits 2 untouched. Commit `.codex/hooks.json` to share the hook.
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
done-done init --agent opencode
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`--agent opencode` instead writes a session-start plugin to the repo's `.opencode/plugins/done-done.js` (project scope; your `~/.config/opencode` is never touched), so the bare `done-done` state is added to the first message of each session, not at session open (opencode's plugin hooks cannot add context when a session is created). The plugin remembers sessions in memory, so a resumed session gets the state again after opencode restarts. That `chat.message` hook runs `command -v done-done >/dev/null && done-done || true` and waits for it (up to 30 seconds) before appending the output to the message: without done-done on PATH it appends nothing and never throws, so it never breaks a session. Other files in `.opencode/plugins/` are untouched; re-running adds no duplicate. The result's `opencode_plugin` says `added to .opencode/plugins/done-done.js` or `unchanged`. Delete the file to opt out. Commit it to share the plugin.
|
|
24
44
|
|
|
25
45
|
### Run a journey
|
|
26
46
|
|
|
@@ -48,7 +68,7 @@ done-done run review
|
|
|
48
68
|
- hashes every file in the run's folder. Every checkout of a repo shares the evidence root, so a run records only its own folder and never another run's files;
|
|
49
69
|
- writes a run record to `<root>/.done-done/runs/<run-id>.json`.
|
|
50
70
|
|
|
51
|
-
A non-zero command exit, or a run that wrote nothing, fails.
|
|
71
|
+
A non-zero command exit, or a run that wrote nothing, fails. A run that wrote nothing names its folder and the two names the command gets it under: `DONE_DONE_EVIDENCE_ROOT` and `DONE_DONE_RUN_ID` (or `${RUN_ID}` in the command).
|
|
52
72
|
|
|
53
73
|
For a proof you drive by hand or through an agent:
|
|
54
74
|
|
|
@@ -59,6 +79,7 @@ done-done run --close <run-id>
|
|
|
59
79
|
```
|
|
60
80
|
|
|
61
81
|
You can also run a one-off command without a journey: `done-done run -- <command>`.
|
|
82
|
+
A run whose command matches no repo-recorded journey says so in its `help`: the recipe is not in the repo, so nobody can re-run the proof from the config. Record it with `done-done init --journey <name> -- <command>` and commit.
|
|
62
83
|
Run `done-done` outside wrappers that expand `${...}` themselves (`systemd-run` does): put such a wrapper inside the command, or it eats `${RUN_ID}`.
|
|
63
84
|
|
|
64
85
|
### Check a claim
|
|
@@ -5,9 +5,10 @@ Before you report done, load the done-done skill and follow it. If the skill is
|
|
|
5
5
|
Run the CLI as `done-done` if it is on PATH; otherwise run `npx -y --allow-git=all github:umeranjum17/done-done-axi`
|
|
6
6
|
wherever this text or the skill says `done-done`. If done-done cannot run, report that instead of writing proof by hand.
|
|
7
7
|
|
|
8
|
-
1. If the repo has no `.done-done.yaml`, run `done-done init
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
1. If the repo has no `.done-done.yaml`, run `done-done init`. Then record a journey that drives the real app and
|
|
9
|
+
writes its screenshots, recordings or logs into `$DONE_DONE_EVIDENCE_ROOT` (done-done sets it to the run's own
|
|
10
|
+
folder): `done-done init --journey <name> -- <command>`, so the recipe lives in the repo and anyone can re-run the
|
|
11
|
+
proof. Commit `.done-done.yaml` and the journey script.
|
|
11
12
|
2. Commit your change, then run `done-done run <journey>` from the repo root. Proof only counts from a clean,
|
|
12
13
|
committed tree.
|
|
13
14
|
3. Write a Checks block (the shape `done-done --help` prints) that cites the run's files by the paths `run` printed:
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# merge-gate <task-id> <worktree
|
|
2
|
+
# merge-gate <task-id> <worktree|-> <head> <pr-url|->
|
|
3
3
|
# Run before a merge: it passes only when done-done passes the task's Checks block and the receipt for <head>.
|
|
4
4
|
# Both a PR and a local-only task (-, read from $FM_HOME/data/<task-id>/checks.md) are checked against the rules
|
|
5
5
|
# on the default branch, never the task's own branch. A nonzero exit refuses the merge; done-done's output says why.
|
|
6
|
+
# A worktree of - means the task recorded no worktree: do not cd, run git and done-done in the current directory
|
|
7
|
+
# (the caller's project clone).
|
|
6
8
|
set -eu
|
|
7
|
-
[ $# -eq 4 ] || { echo "usage: merge-gate <task-id> <worktree
|
|
9
|
+
[ $# -eq 4 ] || { echo "usage: merge-gate <task-id> <worktree|-> <head> <pr-url|->" >&2; exit 2; }
|
|
8
10
|
id=$1 head=$3 pr=$4
|
|
9
|
-
|
|
11
|
+
if [ "$2" != - ]; then
|
|
12
|
+
cd "$2" 2>/dev/null || { echo "merge-gate: cannot enter worktree $2" >&2; exit 2; }
|
|
13
|
+
fi
|
|
10
14
|
ref=$(git symbolic-ref -q --short refs/remotes/origin/HEAD) ||
|
|
11
15
|
for ref in origin/main origin/master main master; do
|
|
12
16
|
git rev-parse -q --verify "$ref^{commit}" >/dev/null && break
|
package/package.json
CHANGED