devlyn-cli 4.3.0 → 4.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skill-history.json +3 -0
- package/config/skills/_shared/harness-store.py +2 -1
- package/config/skills/_shared/task-completion.md +6 -3
- package/config/skills/_shared/warm-build-dirs.py +97 -2
- package/config/skills/devlyn-ideate/scripts/queue.py +10 -1
- package/harness/AGENTS.md +37 -0
- package/harness/CLAUDE.md +37 -0
- package/native/claude/.claude-plugin/plugin.json +6 -0
- package/native/claude/skills/_shared/.ruff.toml +2 -0
- package/native/claude/skills/_shared/adapters/claude.md +15 -0
- package/native/claude/skills/_shared/adapters/codex.md +14 -0
- package/native/claude/skills/_shared/adapters/omp.md +6 -0
- package/native/claude/skills/_shared/codex-monitored.sh +263 -0
- package/native/claude/skills/_shared/engine-doctor.sh +70 -0
- package/native/claude/skills/_shared/engine-preflight.md +29 -0
- package/native/claude/skills/_shared/expected-contract.py +242 -0
- package/native/claude/skills/_shared/expected.schema.json +120 -0
- package/native/claude/skills/_shared/harness-store.py +202 -0
- package/native/claude/skills/_shared/invocation-receipt.py +499 -0
- package/native/claude/skills/_shared/platform-support.py +395 -0
- package/native/claude/skills/_shared/role-config.py +247 -0
- package/native/claude/skills/_shared/runtime-principles.md +35 -0
- package/native/claude/skills/_shared/task-complete.py +2865 -0
- package/native/claude/skills/_shared/task-completion.md +249 -0
- package/native/claude/skills/_shared/warm-build-dirs.py +1065 -0
- package/native/claude/skills/devlyn-engines/SKILL.md +63 -0
- package/native/claude/skills/devlyn-ideate/SKILL.md +132 -0
- package/native/claude/skills/devlyn-ideate/references/elicitation.md +29 -0
- package/native/claude/skills/devlyn-ideate/references/loop.md +123 -0
- package/native/claude/skills/devlyn-ideate/references/package-format.md +128 -0
- package/native/claude/skills/devlyn-ideate/references/plan-add.md +12 -0
- package/native/claude/skills/devlyn-ideate/scripts/acceptance.py +882 -0
- package/native/claude/skills/devlyn-ideate/scripts/queue.py +2191 -0
- package/native/codex/.agents/plugins/marketplace.json +12 -0
- package/native/codex/plugins/devlyn/.codex-plugin/plugin.json +6 -0
- package/native/codex/plugins/devlyn/skills/_shared/.ruff.toml +2 -0
- package/native/codex/plugins/devlyn/skills/_shared/adapters/claude.md +15 -0
- package/native/codex/plugins/devlyn/skills/_shared/adapters/codex.md +14 -0
- package/native/codex/plugins/devlyn/skills/_shared/adapters/omp.md +6 -0
- package/native/codex/plugins/devlyn/skills/_shared/codex-monitored.sh +263 -0
- package/native/codex/plugins/devlyn/skills/_shared/engine-doctor.sh +70 -0
- package/native/codex/plugins/devlyn/skills/_shared/engine-preflight.md +29 -0
- package/native/codex/plugins/devlyn/skills/_shared/expected-contract.py +242 -0
- package/native/codex/plugins/devlyn/skills/_shared/expected.schema.json +120 -0
- package/native/codex/plugins/devlyn/skills/_shared/harness-store.py +202 -0
- package/native/codex/plugins/devlyn/skills/_shared/invocation-receipt.py +499 -0
- package/native/codex/plugins/devlyn/skills/_shared/platform-support.py +395 -0
- package/native/codex/plugins/devlyn/skills/_shared/role-config.py +247 -0
- package/native/codex/plugins/devlyn/skills/_shared/runtime-principles.md +35 -0
- package/native/codex/plugins/devlyn/skills/_shared/task-complete.py +2865 -0
- package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +249 -0
- package/native/codex/plugins/devlyn/skills/_shared/warm-build-dirs.py +1065 -0
- package/native/codex/plugins/devlyn/skills/devlyn-engines/SKILL.md +63 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/SKILL.md +132 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/elicitation.md +29 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +123 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/package-format.md +128 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/plan-add.md +12 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/acceptance.py +882 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/queue.py +2191 -0
- package/package.json +1 -1
- package/release.json +1630 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devlyn-engines
|
|
3
|
+
description: Show and pin the engine that executes devlyn implementation work. Reads and writes machine-local .devlyn/engines.json with fail-closed validation. Use when the user asks which engine is configured, wants to force a specific engine for implementation ("수동모드", "engine 고정", "use codex to implement"), or wants to clear the pin back to the default.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Utility front-end for the executor contract in `_shared/engine-preflight.md`. It adds no semantics of its own.
|
|
7
|
+
|
|
8
|
+
<args>
|
|
9
|
+
$ARGUMENTS
|
|
10
|
+
</args>
|
|
11
|
+
|
|
12
|
+
<runtime_paths>
|
|
13
|
+
Before the first command, establish the bindings below.
|
|
14
|
+
|
|
15
|
+
Resolve bundled resources from the SKILL.md loaded for this invocation.
|
|
16
|
+
|
|
17
|
+
Reader-rendered directory hint:
|
|
18
|
+
```text
|
|
19
|
+
${CLAUDE_SKILL_DIR}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Treat the hint as literal path data, never shell code. If the reader
|
|
23
|
+
replaced it with an absolute directory, use that directory. Otherwise
|
|
24
|
+
use the filesystem path or base directory reported for this loaded
|
|
25
|
+
SKILL.md. Resolve virtual URIs through the reader's native filesystem
|
|
26
|
+
mapping. If available source locations name different directories,
|
|
27
|
+
stop.
|
|
28
|
+
|
|
29
|
+
Bind DEVLYN_SKILL_DIR to that absolute directory. Do not obtain this
|
|
30
|
+
binding from an environment variable, cwd, or another installation.
|
|
31
|
+
Verify its SKILL.md before proceeding. Missing or conflicting source
|
|
32
|
+
identity is BLOCKED:skill-source-unresolved; include the failed path
|
|
33
|
+
when known.
|
|
34
|
+
|
|
35
|
+
Resolve bundled references against this directory. These bindings are
|
|
36
|
+
workflow values: establish them explicitly using each tool or shell's
|
|
37
|
+
literal-path rules, and include their absolute values in every fresh
|
|
38
|
+
worker's prompt, telling it to set them from those values, never from
|
|
39
|
+
its inherited environment. Do not rely on shell state surviving between
|
|
40
|
+
calls.
|
|
41
|
+
|
|
42
|
+
Resolve directory symlinks on DEVLYN_SKILL_DIR before deriving its
|
|
43
|
+
sibling _shared. Bind that directory as DEVLYN_SHARED_DIR. References
|
|
44
|
+
written as _shared/... use this binding. Verify the directory and each
|
|
45
|
+
required resource before use; failure is BLOCKED:shared-dir-unresolved
|
|
46
|
+
with the failed path. Never search another installation.
|
|
47
|
+
|
|
48
|
+
Verify DEVLYN_SHARED_DIR/role-config.py and DEVLYN_SHARED_DIR/engine-doctor.sh before their respective commands. In omp, use `printf '%s\n' skill://devlyn-engines`.
|
|
49
|
+
</runtime_paths>
|
|
50
|
+
|
|
51
|
+
## No args — status
|
|
52
|
+
|
|
53
|
+
1. Run `python3 "$DEVLYN_SHARED_DIR/role-config.py" --workdir "$PWD" --default-engine <this CLI's engine>` and print the executor's engine, source (`engines.json` pin or `default`) and availability. `inactive` lists keys earlier releases wrote for retired pipeline roles: they select nothing, and `clear` removes them. CLI presence is not authentication.
|
|
54
|
+
2. Run `bash "$DEVLYN_SHARED_DIR/engine-doctor.sh"` and show its availability/eligibility table.
|
|
55
|
+
3. Show the subcommands below. Status never launches a model.
|
|
56
|
+
|
|
57
|
+
## Subcommands
|
|
58
|
+
|
|
59
|
+
- `executor <name>` — run the status command with `--set-executor <name>`. It refuses a name with no `_shared/adapters/<name>.md`, or whose adapter declares `executor: no`, and lists the valid names. An engine whose CLI is not available is still pinned, with a warning: pins are promises, and dispatch stops with `BLOCKED:<name>-unavailable` until it is installed.
|
|
60
|
+
- `clear` — run the status command with `--clear`: it removes the executor pin and the inactive keys, preserves unrelated keys, and deletes an empty file.
|
|
61
|
+
- `pair` and `role` were removed with the retired pipeline roles: say so and change nothing.
|
|
62
|
+
|
|
63
|
+
Pass the engine name as one structured argument, never by shell interpolation of user text. After every change, re-print the status. Never modify anything outside `.devlyn/engines.json`.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devlyn-ideate
|
|
3
|
+
description: Loop designer and intent queue. Turns an intent or a document into a validated loop package (a meta-prompt plus self-contained task contracts), queues its tasks, reports queue status, and drains the queue serially with evidence-derived acceptance, recovery and delivery. Operations — plan, add, status, drain; a bare intent is planned and, when the request authorizes implementation, continues through add and drain; no arguments shows status. Use when the user wants an idea, goal or document planned into tasks, wants to stack work ("queue this", "큐에 넣어줘"), asks what is queued, or wants unattended execution ("drain the queue", "큐 드레인 시작", "밤새 돌려줘").
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Ideate owns planning and durable serial execution. Each task runs under the installed methodology, the CLAUDE.md/AGENTS.md instruction block; ideate adds no phase graph, reviewer quota, engine router or restart cycle.
|
|
7
|
+
|
|
8
|
+
<ideate_args>
|
|
9
|
+
$ARGUMENTS
|
|
10
|
+
</ideate_args>
|
|
11
|
+
|
|
12
|
+
<harness_principles>
|
|
13
|
+
`_shared/runtime-principles.md`, mirrored in the installed instruction block (read the file only when no block is loaded), binds package content as well as the conversation: a requirement "for future flexibility" violates Subtractive-first, and acceptance that permits a silent fallback violates No-workaround. Flag these while planning, not after execution.
|
|
14
|
+
</harness_principles>
|
|
15
|
+
|
|
16
|
+
<runtime_paths>
|
|
17
|
+
Before any operation, establish the bindings below.
|
|
18
|
+
|
|
19
|
+
Resolve bundled resources from the SKILL.md loaded for this invocation.
|
|
20
|
+
|
|
21
|
+
Reader-rendered directory hint:
|
|
22
|
+
```text
|
|
23
|
+
${CLAUDE_SKILL_DIR}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Treat the hint as literal path data, never shell code. If the reader
|
|
27
|
+
replaced it with an absolute directory, use that directory. Otherwise
|
|
28
|
+
use the filesystem path or base directory reported for this loaded
|
|
29
|
+
SKILL.md. Resolve virtual URIs through the reader's native filesystem
|
|
30
|
+
mapping. If available source locations name different directories,
|
|
31
|
+
stop.
|
|
32
|
+
|
|
33
|
+
Bind DEVLYN_SKILL_DIR to that absolute directory. Do not obtain this
|
|
34
|
+
binding from an environment variable, cwd, or another installation.
|
|
35
|
+
Verify its SKILL.md before proceeding. Missing or conflicting source
|
|
36
|
+
identity is BLOCKED:skill-source-unresolved; include the failed path
|
|
37
|
+
when known.
|
|
38
|
+
|
|
39
|
+
Resolve bundled references against this directory. These bindings are
|
|
40
|
+
workflow values: establish them explicitly using each tool or shell's
|
|
41
|
+
literal-path rules, and include their absolute values in every fresh
|
|
42
|
+
worker's prompt, telling it to set them from those values, never from
|
|
43
|
+
its inherited environment. Do not rely on shell state surviving between
|
|
44
|
+
calls.
|
|
45
|
+
|
|
46
|
+
Resolve directory symlinks on DEVLYN_SKILL_DIR before deriving its
|
|
47
|
+
sibling _shared. Bind that directory as DEVLYN_SHARED_DIR. References
|
|
48
|
+
written as _shared/... use this binding. Verify the directory and each
|
|
49
|
+
required resource before use; failure is BLOCKED:shared-dir-unresolved
|
|
50
|
+
with the failed path. Never search another installation.
|
|
51
|
+
|
|
52
|
+
Verify DEVLYN_SKILL_DIR/scripts/queue.py before running it. In omp, use `printf '%s\n' skill://devlyn-ideate`.
|
|
53
|
+
</runtime_paths>
|
|
54
|
+
|
|
55
|
+
## Operations
|
|
56
|
+
|
|
57
|
+
| Invocation | Behavior |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `plan <intent or absolute document path>` | Inspect, elicit what is necessary and write a validated loop package. Does not enqueue or execute. |
|
|
60
|
+
| `add <intent or absolute package path>` | Plan when necessary, then capture the package and queue its tasks in dependency order. Does not execute. |
|
|
61
|
+
| `status` | Reconciled pending, active, accepted and failed counts; the next runnable task; delivery and recovery blockers. |
|
|
62
|
+
| `drain` | Resume or drain the queue serially under existing execution authorization. |
|
|
63
|
+
| Bare intent | Plan; when the request authorizes implementation, continue through add and drain without reconfirmation. |
|
|
64
|
+
| No arguments | Status. |
|
|
65
|
+
|
|
66
|
+
Two controls only: `--autonomous` applies the autonomous policy to plan or add (drain always applies it), and `--local-only` keeps a drain's delivery local (`--no-push` is equivalent). A delivery restriction already established for a loop persists. Engine selection and pins stay project and host settings (`/devlyn-engines`).
|
|
67
|
+
|
|
68
|
+
A removed flag stops with its instruction and selects no other behavior.
|
|
69
|
+
|
|
70
|
+
| Flag | Instruction |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `--quick` | Removed: use `plan <intent>`, adding `--autonomous` to plan without questions. |
|
|
73
|
+
| `--from-spec <path>` | Removed: use `plan <absolute path>`; the document stays unchanged as the input contract. |
|
|
74
|
+
| `--project` | Removed: every plan is a loop package with as many tasks as the intent needs. |
|
|
75
|
+
| `--spec-dir`, `--spec-id`, `--in-place` | Removed: packages live at `docs/specs/<loop-id>/` with generated IDs, and input documents are never rewritten. |
|
|
76
|
+
| `--engine` | Removed: drain uses the configured executor; pin it with `/devlyn-engines executor <name>`. |
|
|
77
|
+
|
|
78
|
+
## Question policy
|
|
79
|
+
|
|
80
|
+
> Inspect available project facts before asking. Ask only when the unresolved answer changes authorized behavior, scope, data semantics, acceptance or delivery. State the recommended answer and its consequence. Ask the smallest useful question. Stop eliciting when the contract is executable and verifiable; no turn target or mandatory confirmation applies. Existing authorization remains effective.
|
|
81
|
+
|
|
82
|
+
## Autonomous policy
|
|
83
|
+
|
|
84
|
+
> For ordinary details the intent leaves open within authorized scope, follow established project conventions, otherwise the narrowest literal or conventional reading consistent with the request; these defaults may be user-visible but must be low-consequence and reversible. Record each choice and reason once. Material ambiguity means competing readings with materially different intended outcomes, unresolved persistent data or state semantics, or public surface beyond the request; stop affected work as needs-review with a concrete question. Independent authorized work may continue. Never omit requested work or weaken acceptance to obtain completion.
|
|
85
|
+
|
|
86
|
+
## plan
|
|
87
|
+
|
|
88
|
+
1. Inspect, then elicit, per [elicitation.md](references/elicitation.md). A document argument is the input contract: carry each substantive requirement into a task without weakening it, cite the document in `## Intent`, and never modify it.
|
|
89
|
+
2. Write `docs/specs/<loop-id>/meta.md` and each task's `spec.md` and `spec.expected.json` per [package-format.md](references/package-format.md). In the manifest, `base_ref` is the delivery branch and `base_sha` the exact commit the loop builds on; `delivery` is `local-only` when the user restricted delivery or, recorded as an assumption, when `origin` does not name one GitHub repository; `pr` when the user asked to stop at the PR; `auto` when the user asked to merge, even under a `pr` project policy; else the project's `git config --local devlyn.completionMode` (absent means `auto`). An explicit PR or merge request whose `origin` does not name one GitHub repository stops planning with that question. `## Execution policy` quotes the autonomous policy, which binds every task's executor.
|
|
90
|
+
3. Run `python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" check '<absolute meta.md>'` and repair the package until it reports `VALID`.
|
|
91
|
+
4. Report the package path, the tasks with their dependencies, and every recorded assumption.
|
|
92
|
+
|
|
93
|
+
## add
|
|
94
|
+
|
|
95
|
+
An absolute path to a package's `meta.md` is added as it is; anything else is planned first. Add with `python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" add '<absolute meta.md>'`, the only writer of a loop's queue file `docs/specs/<loop-id>/queue.md`; never edit one by hand. In every delivery mode add makes no commit on the checked-out branch and leaves the branch, the index and tracked files as they are: it holds the package and its rows at `refs/devlyn/captures/<loop-id>`, records the add and removes the package copies equal to that capture. Report the capture ref and its `git show` command, any `cleanup` entry, and that a revision needs a new loop id. A retry of the same add reports it as added, also once the copies are gone. Tasks start from committed state, a local loop from the branch it was added on and `auto`/`pr` from the remote base, so commit, and for `auto`/`pr` push, whatever the plan depends on. A pending legacy raw-intent row of `docs/specs/queue.md` is replaced in place by a package whose `## Intent` reproduces the row verbatim, added with `--materialize <line>`.
|
|
96
|
+
|
|
97
|
+
Release-backed queues retain their verified release in `<git-common-dir>/devlyn-loops/harness.json` until all tasks, recovery and delivery settle. A different release refuses with the exact recovery argv; use it. Existing unfinished unbound queues and standalone installations remain legacy.
|
|
98
|
+
|
|
99
|
+
## status
|
|
100
|
+
|
|
101
|
+
Run `python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" status --repo .` and report its counts, `next`, `blockers`, `warnings`, pending deliveries with their resume commands and any `cleanup` entry.
|
|
102
|
+
|
|
103
|
+
For read-only consumers, `python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" inspect --repo . --json` returns the status summary plus `binding` and `compatibility`, without cleanup, locks or writes.
|
|
104
|
+
|
|
105
|
+
## drain
|
|
106
|
+
|
|
107
|
+
`status`, `add` and `drain` write the repository's Git directory, and auto/pr delivery needs network access, so a sandboxed host runs them with escalation or full access.
|
|
108
|
+
|
|
109
|
+
Follow [loop.md](references/loop.md). The executor is the configured route, the `.devlyn/engines.json` `executor` pin or else this CLI, checked per `_shared/engine-preflight.md`. Pass its argv after `--`. `<git dir>` is the output of `git rev-parse --path-format=absolute --git-common-dir`, where the executor commits and writes its submission; the drain fills `{worktree_git_dir}` with the task worktree's own Git directory, which Codex's sandbox keeps read-only unless that exact directory is listed:
|
|
110
|
+
|
|
111
|
+
| Executor | argv |
|
|
112
|
+
|---|---|
|
|
113
|
+
| Claude | `claude -p "<prompt>" --dangerously-skip-permissions --add-dir "<git dir>"` |
|
|
114
|
+
| Codex | `bash "<DEVLYN_SHARED_DIR>/codex-monitored.sh" --skip-git-repo-check -s workspace-write -c 'sandbox_workspace_write.writable_roots=["<git dir>","{worktree_git_dir}"]' -c 'sandbox_workspace_write.network_access=true' "<prompt>"` |
|
|
115
|
+
|
|
116
|
+
Another engine needs an argv that starts one fresh non-interactive session able, without prompts, to edit, run commands and commit in its task worktree and to write the packet's submission file. `<prompt>` carries the absolute binding values, while `{packet}` stays literal for the drain to fill:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
Execute the devlyn loop task packet {packet}: read its workerPrompt when present; read methodology.defaults.path when named and apply those verified defaults, with explicit user and native owner/project instructions taking precedence; work only in its owned worktree under the installed instructions; meet its obligations, then write its submission and any review records as "Executor exchange" and "Review records" in <DEVLYN_SKILL_DIR>/references/loop.md specify. Use the literal DEVLYN_SKILL_DIR and DEVLYN_SHARED_DIR bindings in workerPrompt when present; otherwise set DEVLYN_SKILL_DIR=<DEVLYN_SKILL_DIR> and DEVLYN_SHARED_DIR=<DEVLYN_SHARED_DIR> from these literal values. Never use inherited environment values. Never push, open a pull request or merge: the drain delivers.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" drain --repo . [--local-only] -- <executor argv>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A drain can run for hours. Do not end the turn until it exits: run it in the foreground with the longest allowed timeout, or in the background and await its completion notice, because a headless host kills background tasks at its final response.
|
|
127
|
+
|
|
128
|
+
- `WAITING` on legacy rows: plan and materialize each under the autonomous policy, then drain again; a row whose planning stops on material ambiguity stays pending and its question is reported.
|
|
129
|
+
- Other `WAITING`: report each task’s reason and recovery command. Task-local failures leave dependents and barrier-held siblings waiting while independent work continues; the next drain retries eligible work once. An interrupted executor is observed once per drain and cannot be adopted or respawned until it stops. `BLOCKED` means authoritative queue reconstruction or preflight failed, including a held drain lock. Never edit receipts, refs or queue rows to get past either result.
|
|
130
|
+
- An interrupted drain is resumed by running `drain` again; accepted work is never replayed, and a failed task is never rerun: plan its work again as a new loop.
|
|
131
|
+
|
|
132
|
+
Report each task's product result, delivery status, PR URL, resume command, assumptions and unresolved questions, plus whole-loop acceptance, the package's `## Decisions and assumptions`, the drain report paths and its exact `Bring into` command; leave running it to the user. Report every `cleanup` diagnostic, including unavailable reports and retained parents, and all `warnings`; completed products keep their verdict. A package copy left in the checkout can prevent the `Bring into` command until it is removed.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Planning and elicitation
|
|
2
|
+
|
|
3
|
+
How `plan` applies the question and autonomous policies in SKILL.md; [package-format.md](package-format.md) defines what it writes. No turn target, question quota or confirmation step applies.
|
|
4
|
+
|
|
5
|
+
## Inspect first
|
|
6
|
+
|
|
7
|
+
Read what the project already answers before asking: the code paths, tests and conventions the intent touches, the installed instructions and completion mode, the queue through `queue.py status` and each added loop's capture (`git for-each-ref refs/devlyn/captures`, then `git show <ref>:docs/specs/<loop-id>/meta.md`) rather than package files in the checkout, and any document the user supplied. Infer `kind` from the framing (investigate or explore → `spike`, a rough version → `prototype`, otherwise `feature`) instead of asking.
|
|
8
|
+
|
|
9
|
+
## What to ask
|
|
10
|
+
|
|
11
|
+
Only an answer that changes authorized behavior, scope, data semantics, acceptance or delivery:
|
|
12
|
+
|
|
13
|
+
- **Behavior** — the exact input, output and failure a user observes. An unspecified user-visible detail alone is not material ambiguity; apply the autonomous policy's boundary.
|
|
14
|
+
- **Scope** — what may change and what must not.
|
|
15
|
+
- **Data semantics** — persisted state, ordering, idempotency, migration.
|
|
16
|
+
- **Acceptance** — the smallest decisive check for each requirement.
|
|
17
|
+
- **Delivery** — local-only, PR or merge.
|
|
18
|
+
|
|
19
|
+
Lead with the recommendation and its consequence, for example: "Should `--lang fr` exit 1 naming the rejected code? Recommended: yes, so bad input stays visible instead of silently falling back to English." Decide preferences the user has no stake in.
|
|
20
|
+
|
|
21
|
+
## Autonomous planning
|
|
22
|
+
|
|
23
|
+
With `--autonomous`, or when a drain plans a legacy row, take only the defaults the autonomous policy allows and record each once under `## Decisions and assumptions`. Material ambiguity stops planning with its concrete question.
|
|
24
|
+
|
|
25
|
+
## Tasks and checks
|
|
26
|
+
|
|
27
|
+
- One task per independently verifiable deliverable.
|
|
28
|
+
- A check must fail on the wrong behavior. When requirements interact (state with ordering, idempotency, error priority or an exact output shape), add one compound check that exercises the interaction end to end. Validate each new guard with a violating control that fails and an allowed control that passes.
|
|
29
|
+
- Semantic or source judgments are review requirements, not regular expressions. A requirement that resists a mechanical check becomes a review requirement; it is never weakened into something checkable.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Loop protocol
|
|
2
|
+
|
|
3
|
+
`drain` runs the queue serially. Each eligible task gets an owned worktree, committed inputs, one executor exchange, evidence-derived acceptance, a queue-only terminal commit and delivery. The executor works under the installed methodology (the CLAUDE.md/AGENTS.md instruction block). Formats: [package-format.md](package-format.md).
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" status --repo .
|
|
9
|
+
python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" drain --repo . [--local-only] [--worktree-root <dir>] -- <executor argv containing {packet}>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Both print one JSON object; exit 1 is `BLOCKED` with a `reason`.
|
|
13
|
+
|
|
14
|
+
- `status` takes `queue.lock` in the repository's Git directory and finishes `add`'s cleanup (step 1); it reports the running `skill` root and version, any `warnings`, reconciled counts (`pending`, `active`, `accepted`, `failed`, `blocked`, `legacy_pending`), the `next` task, `blockers`, deliveries still pending with their resume (drain again until the terminal commit is attached, then the printed completion command; add `--writers-stopped` only after stopping the task's writers), and under `cleanup` the package copies `add` kept or could not remove.
|
|
15
|
+
- `drain` ends `DRAINED` (no runnable task remains; open deliveries show per task), `WAITING` (a task waits with its reason, or legacy rows need planning) or `BLOCKED` (unreadable or ambiguous authoritative queue, invalid invocation, preflight refusal or held drain lock). Progress lines `devlyn-loop: <task>: <event>` go to stderr.
|
|
16
|
+
- `--local-only` (alias `--no-push`) keeps the loop local. A manifest `delivery: local-only` and any earlier local receipt of the loop keep that restriction for later drains. A local loop needs no remote; nothing is fetched or pushed. An `auto`/`pr` loop none of whose tasks has a pushed PR runs as a local loop from its capture and the branch it was added on. Once one has, that PR is never rewritten to local and the loop is never run locally: its tasks wait, naming the PR, while other work continues.
|
|
17
|
+
- Worktrees default to `<repo parent>/<repo name>.devlyn.noindex/<loop-id>/<task-id>`; tasks already allocated under an earlier default resume where their receipts record them. On Windows, a default root newly created by the drain gets the not-content-indexed attribute, inherited by new children (content only; names stay indexed). Microsoft Defender scanning is unaffected; use a Dev Drive via `--worktree-root` or an administrator's Defender exclusion for the root.
|
|
18
|
+
|
|
19
|
+
The host supplies the executor and maps its configured engine to the argv; the loop has no model preference. Each `{packet}` in the argv is replaced by the task packet path, and each `{worktree_git_dir}` by the task worktree's absolute Git directory. The executor runs with the task worktree as its working directory and the null device as stdin; its stdout and stderr are appended to `executor.stdout` and `executor.stderr` in the packet's `evidence_dir`, never a pipe.
|
|
20
|
+
|
|
21
|
+
`<common Gitdir>/devlyn-loops/drain.lock` is held for the controller's lifetime, so a second drain from any worktree of the repository is refused. `queue.lock` is held only to read the add records and queue files, to add a loop and to make terminal transitions, so `add` works during a drain.
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
1. **Reconcile.** First finish `add`'s cleanup of package copies ([package-format.md](package-format.md) Commands). Then read the queue view: the legacy rows, each replaced row as its loop's rows, then each loop's rows, from its capture or, without a record, from the checkout's tracked queue file ([package-format.md](package-format.md) Queue rows); every `<common Gitdir>/devlyn-completion/*/receipt.json` and their recovery refs. A row's identity, its receipt's branch `devlyn/<loop-id>/<task-id>`, the recovery ref (equal to the receipt's `publish_sha`, or to its validated terminal commit while that attachment is unfinished), the terminal commit's mark and, while active, the committed contract digests must agree. A receipt's terminal result supplements a `[ ]` row. Receipt, recovery-ref and packet conflicts invalidate the implicated tasks with an inspection/recovery reason; a missing queued prerequisite invalidates its dependent. Invalid evidence proves neither acceptance nor a frontier. A possible writer or package carrier retains its loop’s safety barrier while independent loops continue. An occupied receipt directory without a receipt waits for verified interrupted-allocation recovery; nothing is fabricated, rewritten or deleted. An unfinished allocation makes its task wait (step 10). Unreadable authoritative queue bytes, duplicate identities, missing/malformed queue files and invalid add records block the whole drain. A captured package never changes; for a loop without a record, an active task whose contract or expected acceptance changed in the checkout, also when that text is now missing or invalid, becomes `[F] inputs-changed` without executing again, its workspace retained, other work continuing while the revision is planned as a new task, and a pending task whose package text fails validation waits with that error, as do its dependents. Unsettled receipts (active, terminal commit unattached, or delivery not final) resume before any new allocation.
|
|
26
|
+
2. **Select** the earliest pending row whose prerequisites are accepted with a receipt in every mode (a hand-written `[x]` never counts) and, for `auto`/`pr`, delivered (merged). A failed or blocked prerequisite makes its dependent `[F] blocked-prerequisite:<id>` without invoking the executor; that derived mark is proven by the prerequisite's receipt and travels into later checkouts and the report. A prerequisite awaiting delivery leaves its dependent pending while independent work continues; so does an `auto`/`pr` carrier in flight (step 3), or a local loop's active task (a local loop runs one task at a time), for every other task of its loop. Legacy rows wait for `add --materialize`.
|
|
27
|
+
3. **Allocate** a new owned worktree on the absent branch `devlyn/<loop-id>/<task-id>` with `task-complete.py allocate`. The commit the task starts from must hold this checkout's `CLAUDE.md` and `AGENTS.md` (each identical, or both absent), so the task runs under the installed instructions, and, where it holds the loop's queue file, every captured package file unchanged, so the task binds its captured contract; otherwise the task waits, naming the fix, while independent work continues.
|
|
28
|
+
- Local: the first task uses `--local-base <HEAD of the branch the loop was added on>`, the add record's branch, never whichever branch is checked out; that HEAD must descend from the manifest `base_sha`, or the task waits naming it. Instructions committed there before this first allocation are therefore used. Its receipt keeps that start, and a later task with no accepted predecessor reuses it, also after the first task failed; a fixed start is never recalculated. Later tasks use `--from-receipt <latest accepted receipt of the loop>`, which starts from that receipt's exact `source_sha`, never its terminal commit, current HEAD or workspace. Every prerequisite's accepted source must be an ancestor of that frontier; divergent dependencies need a planned integration task, because the driver never merges. A failure leaves the frontier unchanged.
|
|
29
|
+
- `auto`/`pr`: `--start` the refreshed remote base, never an anchor-local commit, so no unpushed anchor commit reaches a task PR; push whatever the plan depends on. A task allocated while that base lacks `docs/specs/<loop-id>/` is the loop's carrier; a carrier that fails is never published, and the next task allocated becomes the carrier. The start must contain each prerequisite's merge commit, checked before allocation and again before every execution or resume. A squash merge needs no original-source ancestry. A refresh failure waits for restored remote access or the base ref. A missing merge before allocation waits for that merge to be restored on the remote base; an allocated baseline is immutable and needs a new loop or verified recovery of a never-executed allocation.
|
|
30
|
+
4. **Commit scoped inputs** on the owned branch before any executor write, only what the allocation base lacks. A base without `docs/specs/<loop-id>/` gets the package directory from the capture with the loop's queue file, its settled rows keeping their marks (receipt-proven `[x]` and `[F]`, prerequisite-blocked `[F]`): a local loop's first task, or an `auto`/`pr` carrier, whose PR lands the package before tasks diverge. Otherwise a local task commits the base's queue file with the loop's receipt-proven terminal rows (typically a predecessor's `[x]`), every other byte unchanged, and an `auto`/`pr` task commits nothing, so its PR changes only its own row and its product. A base that already carries them gets no inputs commit; unrelated product commits stay out.
|
|
31
|
+
5. **Exchange one packet.** Verify the bound release, defaults, packet and worker prompt before execution or recovery; changed or missing resources refuse dispatch. Write `<receipt dir>/packet.json`, then run the executor. An executor that exits without writing its submission leaves its task active, waiting with the exit code and `executor.stderr`, and the next drain runs it again; one that cannot start keeps its allocation, packet and durable start/not-started log, waits for executable, permissions or workspace repair, and retries on the next drain.
|
|
32
|
+
6. **Derive acceptance** with `acceptance.py accept`. It binds the committed contract to the packet digests; requires the candidate to be the owned branch head with HEAD checked out on that branch, to descend from the inputs, to leave the package (its queue file included) and `docs/specs/queue.md` untouched and to have a clean worktree; executes each declared command on that source, recording raw stdout/stderr and exit, timeout or spawn outcomes (a runner result is reused only when it is wholly clean — no reasons, so no failed check and no source change during its checks — for the same source and contract digests, with unchanged streams); evaluates the file and diff guards against the inputs; confirms the source is unchanged; checks review records; and writes `.devlyn/loop/acceptance.json`. Missing checks, missing review coverage and open binding findings cannot produce `[x]`, and acceptance weakening is not repair. This establishes command outcomes and evidence completeness, not a reviewer's semantic judgment; deliberate evidence forgery is outside the trust model.
|
|
33
|
+
7. **Bind before terminal metadata.** `task-complete.py accept --receipt <receipt> --acceptance <worktree>/.devlyn/loop/acceptance.json` takes evidence custody and points the recovery ref at the source. Failed results are bound the same way; they never become a frontier or a publishable product.
|
|
34
|
+
8. **Commit the terminal transition** under the queue lock: one commit whose sole parent is the bound source and whose only change is this task's row in `docs/specs/<loop-id>/queue.md` (`[x]`, or `[F] — <first reason> (receipt <id>)`), validated as the only legal transition. Then `task-complete.py attach --receipt <receipt> --commit <terminal> --file docs/specs/<loop-id>/queue.md` records it separately from the source and moves the recovery ref to it.
|
|
35
|
+
9. **Deliver** after waiting for the executor, with `task-complete.py complete --receipt <receipt> --writers-stopped`, plus `--local-only` or, until the receipt records a mode, the packet's `--mode auto|pr`, so a per-task `--mode` persists. Local-only keeps source, custody and the recovery ref with no remote effect. `auto`/`pr` publish the terminal commit under the completion contract; a pending or refused delivery keeps product acceptance, resources and a resume command. Where writer cessation cannot be observed, or a process still uses the tree, a merged delivery settles (`COMPLETE`) with its workspace, task ref and custody retained and reported under workspace cleanup; retained cleanup never blocks the queue. Failed products are never published. The receipt's `delivery` field checkpoints the latest outcome. A local chain is never turned into stacked PRs; publishing it requires integrated acceptance of the actual publication candidate.
|
|
36
|
+
10. **Recover** from durable state only:
|
|
37
|
+
|
|
38
|
+
| Interrupted | Resume |
|
|
39
|
+
|---|---|
|
|
40
|
+
| During allocation | A receipt without `allocation: owned` is never adopted: its task waits, naming the recovery (remove its worktree and branch if present, delete the receipt directory, drain again), while independent work continues. |
|
|
41
|
+
| Inputs commit | A branch head equal to the expected inputs tree is adopted; anything else leaves the task waiting for inspection, preserving the unexpected commits. |
|
|
42
|
+
| During execution | Each attempt's start is recorded before its spawn and its exit after it. A start without an exit gets one writer observation per drain. A live writer leaves the task waiting while independent work continues; a later drain adopts the submission or runs the executor again only after cessation is established; where writers cannot be observed (native Windows) the task becomes `[F] interrupted-unobservable` with its workspace retained, its dependents become prerequisite-blocked and independent work continues. Exactly-once execution is not promised. |
|
|
43
|
+
| During checks | Acceptance reruns; incomplete evidence stays unreferenced. |
|
|
44
|
+
| During custody binding | Acceptance reruns while the receipt is unbound. Any unbound custody copy, complete or partial, is set aside in a unique hidden sibling before fresh custody is published; it and orphan staging copies stay retained and unreferenced. A bound receipt resumes its original result with strict evidence validation. |
|
|
45
|
+
| Accepted, before terminal commit | Only the missing transition is created. |
|
|
46
|
+
| Terminal committed, before attachment | The commit is validated and attached, also when attachment already moved the recovery ref; nothing reruns. |
|
|
47
|
+
| Terminal committed, before delivery | Only the missing delivery effects run. |
|
|
48
|
+
| After delivery, before cleanup | Owned cleanup resumes; the product verdict is unchanged. After confirmed worktree removal, drain prunes empty parents strictly below its matching worktree root, preserving that root, nonempty directories and symlinks. Later drains revisit completed receipts to finish interrupted parent pruning without execution. |
|
|
49
|
+
|
|
50
|
+
Expected task-local operational failures leave the selected task waiting with `<phase> blocked: <detail>` and recovery guidance. It is attempted at most once per drain; dependents, local siblings behind an active task, and siblings behind an undelivered package carrier wait too. Independent work continues and the drain ends `WAITING`. Inputs, acceptance, custody and terminal-attachment failures resume only their missing effects; bound results and accepted work never replay. The controller boundary contains `LoopError` and `OSError`; other programming errors propagate there. The inherited task-complete wrapper and delivery-repository lookup also contain helper `TypeError`, `KeyError` and `ValueError`, converting them to task waits.
|
|
51
|
+
|
|
52
|
+
11. **Report** to `<common Gitdir>/devlyn-loops/<loop-id>/drain-report.md` after every drain: per task the product result and reason, receipt, evidence custody and recovery ref, allocation base, accepted (or unaccepted) source, terminal commit, delivery status, PR URL and resume command, assumptions and unresolved questions, the retained worktree and branch, and workspace and scratch cleanup. Include the package's decisions and assumptions once, excluding the manifest and separately from executor assumptions; read the captured package when an add record exists, otherwise use the checkout, and report an unreadable package explicitly. Whole-loop acceptance is reported separately over the manifest's complete task set: every task needs receipt-backed acceptance, including the integration task's assembled-product check on the final frontier; a manifest task missing from the queue is reported as not yet run. `Bring into` names the command that brings the accepted frontier into the branch the loop was added on. For a local loop it is `git merge --ff <final frontier branch>` on that branch, which fast-forwards when it can and merges otherwise, so the commands of several local loops apply in either order. For `auto`/`pr`, once the frontier is delivered, it is `git merge --ff origin/<base>` on `<base>`: the anchor holds no loop commit or copy, so it fast-forwards after merge, squash and rebase delivery alike unless that branch has commits of its own, and merges then.
|
|
53
|
+
|
|
54
|
+
Package cleanup, parent pruning and report failures appear as diagnostics under `cleanup`, with retained paths and repair guidance; they do not change product acceptance or completed delivery. The next drain retries them.
|
|
55
|
+
|
|
56
|
+
## Release binding and read-only consumers
|
|
57
|
+
|
|
58
|
+
`<git-common-dir>/devlyn-loops/harness.json` is authoritative across linked worktrees: `{schemaVersion: 1, release: {package, version, digest, root}, defaults: {path, sha256}}`. Paths are absolute and hashes cover exact bytes. The queue lock covers binding before the first capture. An interrupted first add, unfinished tasks, recovery conflicts and pending delivery retain it; settlement removes it. A different release refuses with `recovery argv` as a JSON argv array, preserving the caller’s arguments. No caller substitutes another implementation.
|
|
59
|
+
|
|
60
|
+
`queue.py inspect --repo <dir> --json` reads without cleanup, locks or writes. It returns the status summary plus `binding` (null for legacy) and `compatibility`: `compatible`, `legacy`, `reasons`, `recoveryArgv`, `taskPacket` ranges and `queueBinding` ranges. Incompatible or unreadable state is reported, never migrated. Apps should use this reader instead of `status`, which still reconciles cleanup. For a mismatch, `recoveryArgv` names the bound queue’s inspect invocation; use the same bound queue entry point for authorized add/drain operations.
|
|
61
|
+
|
|
62
|
+
Codex projection writers place `.devlyn-harness-projection.json` at the copied plugin root, beside `skills` and `.codex-plugin`: `{schemaVersion: 1, release: <ReleaseRef>, source: "native/codex/plugins/devlyn"}`. The reader verifies the named release and every projected file’s bytes/mode against that subtree (excluding only the marker). Symlinked skill invocations resolve through their real path. Standalone installs and existing unfinished unbound queues retain legacy behavior; new legacy packets use v2 with a null release, and their `defaults` name the invoking installation's own `_shared/runtime-principles.md` (`source: "skill"`). No engine applicability is inferred from instruction filenames: bound queues use verified release defaults with native instructions taking precedence. Worker and runner paths point to the bound release, independent of a projection cache’s lifetime.
|
|
63
|
+
|
|
64
|
+
## Executor exchange
|
|
65
|
+
|
|
66
|
+
The packet (`<receipt dir>/packet.json`) carries everything a fresh executor needs:
|
|
67
|
+
|
|
68
|
+
| Field | Meaning |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `task`, `title`, `kind` | Queue identity and deliverable description. |
|
|
71
|
+
| `worktree`, `branch`, `receipt`, `scratch` | Absolute owned worktree, branch, receipt and disposable build directory. |
|
|
72
|
+
| `allocation_base`, `inputs_sha` | Allocation base and the committed-inputs commit. |
|
|
73
|
+
| `contract`, `expected`, `meta` | `{path, sha256}` of the committed task contract, expected acceptance and meta-prompt. |
|
|
74
|
+
| `requirements`, `review_requirements` | Requirement IDs and those needing review evidence. |
|
|
75
|
+
| `shared_constraints` | The meta-prompt's `Constraints and exclusions`. |
|
|
76
|
+
| `dependencies` | `{task, receipt, source_sha}` of each accepted prerequisite. |
|
|
77
|
+
| `schema_version`, `harness` | New packets use version 2 and the bound release reference, or null for legacy unbound queues. Existing v1 packets execute unchanged. |
|
|
78
|
+
| `methodology` | `projectInstructions`: `{path, sha256}` of instruction bytes at the inputs commit. `defaults`: `{source: "release", path, sha256}` from the verified binding; for an unbound queue `{source: "skill", path, sha256}` of the installation's own `_shared/runtime-principles.md`. A task whose executor would get no principles at all (no defaults and no committed CLAUDE.md/AGENTS.md) waits with that reason instead of running. Read the named defaults; explicit user and native owner/project instructions take precedence. No native engine applicability is inferred from filenames. |
|
|
79
|
+
| `workerPrompt` | `{path, sha256}` of the task’s worker instructions, or null for legacy unbound queues. Read it before working. |
|
|
80
|
+
| `delivery` | `local-only`, `auto` or `pr`. |
|
|
81
|
+
| `evidence_dir`, `runner` | Where loop evidence lives (`<worktree>/.devlyn/loop`, ignored by Git) and the runner argv. |
|
|
82
|
+
| `submission`, `obligations` | Where to write the submission, and the completion obligations. |
|
|
83
|
+
|
|
84
|
+
Writable tool caches go under `scratch`, for example `npm_config_cache=<scratch>/npm`: Codex's sandbox keeps caches in HOME read-only.
|
|
85
|
+
|
|
86
|
+
Build in the worktree's default build directory, never one redirected into `scratch` or shared with another checkout: drain re-runs the checks without your environment. Allocation may have warmed it (`build_dirs` in the receipt).
|
|
87
|
+
|
|
88
|
+
Run `runner` (`acceptance.py run --packet <packet>`) on the committed candidate before review. It prints `{"result": <path>, "passed": ..., "reasons": [...]}`; listing a wholly clean result (empty `reasons`) lets drain reuse its outcomes. Then write the submission:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"schema_version": 1,
|
|
93
|
+
"task": "<identity>",
|
|
94
|
+
"source_sha": "<candidate commit on the owned branch>",
|
|
95
|
+
"runner_results": ["<worktree>/.devlyn/loop/runs/<n>/result.json"],
|
|
96
|
+
"reviews": ["<worktree>/.devlyn/reviews/<name>.json"],
|
|
97
|
+
"assumptions": ["<each assumption, recorded once>"],
|
|
98
|
+
"blockers": []
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`schema_version`, `task` and `source_sha` are required. A blocker `{"kind": "failed" | "needs-review" | "blocked-infrastructure", "detail": "<concrete question or cause>"}` records `[F]` with that reason and runs no checks; material ambiguity becomes `needs-review`, never a weakened acceptance.
|
|
103
|
+
|
|
104
|
+
## Review records
|
|
105
|
+
|
|
106
|
+
Each `review_requirements` ID needs review evidence: a JSON record under `<worktree>/.devlyn/`, listed in the submission's `reviews`, in this shape:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"schema_version": 1,
|
|
111
|
+
"kind": "devlyn-review",
|
|
112
|
+
"task": "<identity>",
|
|
113
|
+
"engine": "<engine>",
|
|
114
|
+
"model": "<exact model id>",
|
|
115
|
+
"source_sha": "<reviewed commit>",
|
|
116
|
+
"contract_sha256": "<packet.contract.sha256>",
|
|
117
|
+
"expected_sha256": "<packet.expected.sha256>",
|
|
118
|
+
"requirements": ["R3"],
|
|
119
|
+
"findings": [{"id": "F1", "binding": true, "disposition": "resolved", "requirement": "R3", "summary": "<finding>"}]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
A record counts only when it binds this task, the candidate source and both contract digests; any other well-formed record is reported under `ignored_reviews` (a source change invalidates earlier reviews). A listed review or runner record that is unreadable or not this shape fails acceptance with its path named; keys a review record or finding adds beyond this shape are ignored. The counted records' `requirements` must cover `review_requirements`. Each finding's `disposition` is `open`, `resolved` or `rejected` (`rejected` needs a `reason`); an open binding finding blocks acceptance. The loop requires no particular engine or number of reviewers.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Loop package format
|
|
2
|
+
|
|
3
|
+
The formats `scripts/queue.py check|add` validate. One package per intent:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
docs/specs/<loop-id>/meta.md index: intent, shared constraints, manifest
|
|
7
|
+
docs/specs/<loop-id>/<task-id>/spec.md self-contained task contract
|
|
8
|
+
docs/specs/<loop-id>/<task-id>/spec.expected.json mechanical acceptance
|
|
9
|
+
docs/specs/<loop-id>/queue.md the loop's queue rows, generated by add
|
|
10
|
+
docs/specs/queue.md legacy raw intents: read for materialize, never written
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
IDs match `[a-z0-9][a-z0-9-]{0,62}`. The queue identity `<loop-id>.<task-id>` never changes and is never reused; revised work gets new IDs. Headings below are fixed ASCII identifiers, like the verification sentinel; their content may be in any language.
|
|
14
|
+
|
|
15
|
+
## meta.md
|
|
16
|
+
|
|
17
|
+
Required `##` sections, each non-empty:
|
|
18
|
+
|
|
19
|
+
| Heading | Content |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `Intent` | Original intent and referenced source documents (input documents are preserved, not rewritten); intended user outcome. |
|
|
22
|
+
| `Constraints and exclusions` | Shared requirements, prohibited changes, applicable delivery restrictions. Sent to every worker. |
|
|
23
|
+
| `Tasks` | Ordered task index, dependency graph and decomposition rationale. No prescribed task count; one task is a valid loop. |
|
|
24
|
+
| `Overall acceptance` | Every overall requirement assigned to a task; cross-task requirements assigned to the integration task's assembled-product check. |
|
|
25
|
+
| `Execution policy` | Baseline, installed-methodology rule, autonomous question policy, delivery policy, stop conditions. |
|
|
26
|
+
| `Decisions and assumptions` | Adopted decisions and defaults with reasons. No elicitation transcript. Put defaults affecting a task's observable behavior in that task's requirements and acceptance checks. |
|
|
27
|
+
|
|
28
|
+
Exactly one fenced `json` block, the manifest. It is authoritative for order and dependencies and never carries status:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"schema_version": 1,
|
|
33
|
+
"loop_id": "inventory",
|
|
34
|
+
"base_ref": "main",
|
|
35
|
+
"base_sha": "<exact 40- or 64-hex commit in this repository>",
|
|
36
|
+
"delivery": "auto",
|
|
37
|
+
"tasks": [
|
|
38
|
+
{"id": "t01", "spec": "t01/spec.md", "depends_on": []},
|
|
39
|
+
{"id": "t02", "spec": "t02/spec.md", "depends_on": ["t01"]}
|
|
40
|
+
],
|
|
41
|
+
"integration_task_id": "t02"
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- Keys are exactly these; `loop_id` equals its directory; each `spec` is `<id>/spec.md`.
|
|
46
|
+
- `delivery` is the inherited `auto` or `pr`, or an explicit `local-only` ([loop.md](loop.md) step 3).
|
|
47
|
+
- `depends_on` names earlier tasks; unknown dependencies, cycles and forward references are rejected before execution.
|
|
48
|
+
- `integration_task_id` is the last task and depends, directly or transitively, on every other task; its acceptance is the assembled-product check. Assign integration to the final substantive task when its substantive deliverable already depends, directly or transitively, on every other task. Otherwise preserve the substantive tasks' dependencies and append a check-only task that depends on all of them and verifies the assembled product without changing product source; it declares the full-diff guard `{"pattern": ".", "description": "No source changes", "severity": "disqualifier"}` in `forbidden_patterns`.
|
|
49
|
+
|
|
50
|
+
## Task spec.md
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
---
|
|
54
|
+
id: t02
|
|
55
|
+
loop_id: inventory
|
|
56
|
+
title: Reservation behavior
|
|
57
|
+
kind: feature
|
|
58
|
+
review_requirements: [R3]
|
|
59
|
+
---
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`kind` (`feature`, `spike`, `prototype`) describes the deliverable and routes nothing. `review_requirements` lists requirement IDs that need semantic or source judgment (`[]` when none). An optional `complexity` is descriptive only. `status` is rejected: queue state lives in the queue and receipts.
|
|
63
|
+
|
|
64
|
+
Required `##` sections, each non-empty:
|
|
65
|
+
|
|
66
|
+
| Heading | Content |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `Context and goal` | Outcome and why this task exists. |
|
|
69
|
+
| `Requirements` | One list item per obligation with a stable ID: `- R1: <concrete acceptance obligation>`. |
|
|
70
|
+
| `Scope and constraints` | Authorized implementation surface and the shared constraints that apply. |
|
|
71
|
+
| `Out of scope` | Explicit exclusions. |
|
|
72
|
+
| `Prerequisite inputs` | Predecessor interfaces, artifacts and assumptions this task relies on. |
|
|
73
|
+
| `Verification` | Commands, assertions and requirement coverage. The line directly above the heading is `<!-- devlyn:verification -->`. |
|
|
74
|
+
| `Deliverable and cleanup` | Result to retain, change-created residue to remove, permitted disposable output. |
|
|
75
|
+
|
|
76
|
+
A worker receives this file and its task packet only, never the planning conversation or other task specs, so state everything it needs here.
|
|
77
|
+
|
|
78
|
+
## spec.expected.json
|
|
79
|
+
|
|
80
|
+
Schema: [expected.schema.json](../../_shared/expected.schema.json), parsed strictly (duplicate keys and non-finite numbers are rejected) by `_shared/expected-contract.py`. Loop tasks use:
|
|
81
|
+
|
|
82
|
+
- `verification_commands`, each with exactly one of `argv` (preferred: no shell, portable to Windows) or `cmd` (shell); `exit_code` (default 0); `timeout_sec` (1-600, default 60); `stdout_contains` and `stdout_not_contains` (UTF-8 substrings of stdout plus stderr); and non-empty `contract_refs` naming this task's requirement IDs.
|
|
83
|
+
- `required_files` and `forbidden_files` (literal Git paths), `forbidden_patterns` (regex over the task's own diff; `disqualifier` blocks acceptance, `warning` is recorded), `max_deps_added` (default 0; counts only the root `package.json`, so list other manifests in `forbidden_files`) and `pure_design`.
|
|
84
|
+
|
|
85
|
+
Rejected otherwise:
|
|
86
|
+
|
|
87
|
+
- Every requirement is covered by some command's `contract_refs`, by `review_requirements`, or both.
|
|
88
|
+
- A runtime task has at least one command. `pure_design: true` has no commands and lists every requirement in `review_requirements`.
|
|
89
|
+
- Phase-specific obligations (`process_evidence`, `required_risk_probe_requirements`, `tier_a_waivers`, `spec_output_files`) are rejected with an instruction to translate them into commands or review requirements without weakening them.
|
|
90
|
+
|
|
91
|
+
Coverage proves that every obligation has evidence. Whether the checks express the owner's intent stays a planning and review responsibility.
|
|
92
|
+
|
|
93
|
+
## Queue rows
|
|
94
|
+
|
|
95
|
+
`add` generates the loop's `docs/specs/<loop-id>/queue.md` into its capture (Commands), never into the checkout: one row per task in manifest order, each line one blank line from the next, ending in one newline.
|
|
96
|
+
|
|
97
|
+
```markdown
|
|
98
|
+
- [ ] inventory.t01 [Stock model](docs/specs/inventory/t01/spec.md)
|
|
99
|
+
|
|
100
|
+
- [x] inventory.t02 [Reservation behavior](docs/specs/inventory/t02/spec.md)
|
|
101
|
+
|
|
102
|
+
- [F] inventory.t03 [Report](docs/specs/inventory/t03/spec.md) — blocked-prerequisite:inventory.t02 (receipt <id>)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- Links are repository-relative and exactly `docs/specs/<loop-id>/<task-id>/spec.md`; titles escape `\`, `[` and `]`. Packets and reports carry absolute paths.
|
|
106
|
+
- `[ ]` is pending or active (the receipt tells which). `[x]` is an accepted product. `[F]` is failed, infrastructure-blocked, prerequisite-blocked or needs-review and ends with ` — <reason> (receipt <id>)`. Delivery state lives in receipts and the drain report; a pending PR never turns `[x]` into `[F]`.
|
|
107
|
+
- The only legal transition turns one pending row into `[x]` or `[F]` with every other byte unchanged, so a terminal commit changes one line of its loop's file. Terminal rows are never reinterpreted or rerun. The blank lines let transitions of adjacent rows merge, and loops never share a file.
|
|
108
|
+
- Identities are unique; equal titles are fine.
|
|
109
|
+
- `docs/specs/queue.md` is the legacy queue. Its raw rows are intents: readable, never executed, never written (a row there that fully matches this grammar is ignored). `add --materialize <line>` plans one pending legacy row as a loop whose `## Intent` reproduces it verbatim (whitespace may reflow); that loop's queue file starts with `<!-- replaces docs/specs/queue.md row (occurrence <n>): <row> -->`, the row's exact text and its occurrence among equal rows, line endings aside. Every checkout, a fresh clone included, then shows the loop's rows in that row's place. A claim matching no row replaces nothing; a row two loops claim (two clones each materializing it) is hidden, neither loop taking its place, so it is never planned again.
|
|
110
|
+
- Status and drain read the legacy rows in file order, each replaced row as its loop's rows; then the other recorded loops in add order, from their captures; then loops without a record here, as in a fresh clone, from the checkout's tracked queue files in loop-id order.
|
|
111
|
+
|
|
112
|
+
## Commands
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" check '<repo>/docs/specs/<loop-id>/meta.md'
|
|
116
|
+
python3 "$DEVLYN_SKILL_DIR/scripts/queue.py" add '<repo>/docs/specs/<loop-id>/meta.md' [--materialize <line>]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`check` validates without writing. Both print one JSON object; exit 1 is `BLOCKED` with a `reason`. `add` holds the queue lock and, in every delivery mode, makes no commit on the checked-out branch and changes no branch, index entry or tracked file:
|
|
120
|
+
|
|
121
|
+
1. It resolves an add record for the loop id first, with or without the package files: the same package (every remaining copy equal to the capture, line endings aside, and the same `--materialize` row) reports `ADDED` and changes nothing; another package is refused as identity reuse. A capture ref a crash left without a record is captured again only when the checkout's package equals it; otherwise add reports that state.
|
|
122
|
+
2. It validates the package and refuses a loop id that a committed `docs/specs/<loop-id>/queue.md` already uses, package files committed with different content, a detached HEAD, and a HEAD whose `CLAUDE.md` or `AGENTS.md` differs from this checkout's.
|
|
123
|
+
3. It refuses package changes staged in the index, which it would leave behind; unstage them (`git restore --staged -- docs/specs/<loop-id>`) and add again.
|
|
124
|
+
4. It builds a tree of the package files and the generated queue file in a temporary index, commits it, and points `refs/devlyn/captures/<loop-id>` at the commit, which keeps it from garbage collection.
|
|
125
|
+
5. It publishes `<common Gitdir>/devlyn-loops/<loop-id>/added.json` atomically: the loop, its delivery, the capture ref, commit and tree, the checkout and branch it was added on, any materialize target and an add sequence.
|
|
126
|
+
6. It removes the untracked package files equal to the capture, line endings aside. A changed copy is kept and a failed removal is reported as incomplete, both under `cleanup`; every later `add`, `status` and `drain` finishes the cleanup.
|
|
127
|
+
|
|
128
|
+
The result names the capture: the package is held immutably at `refs/devlyn/captures/<loop-id>` (inspect it with `git show refs/devlyn/captures/<loop-id>:docs/specs/<loop-id>/meta.md`), and a revision needs a new loop id. While a loop has a record, validation, the active-contract check, scoped inputs, packets, reports and whole-loop acceptance read its capture; a loop without a record reads the checkout's tracked files.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Planning and capture only
|
|
2
|
+
|
|
3
|
+
This reference grants plan and add operations only. Never drain, implement, run an executor or deliver code from this planning session.
|
|
4
|
+
|
|
5
|
+
Resolve this reference’s actual containing skill directory (the parent of `references`), resolving symlinks; bind it as `DEVLYN_SKILL_DIR`. Resolve its sibling `_shared` as `DEVLYN_SHARED_DIR`. Verify the loaded files; missing or conflicting source identity is `BLOCKED:skill-source-unresolved`. Never substitute another installation or inherited environment values.
|
|
6
|
+
|
|
7
|
+
1. Inspect project facts and follow [elicitation.md](elicitation.md). Ask only when the unresolved answer changes authorized behavior, scope, data semantics, acceptance or delivery. Existing authorization remains effective. Under autonomous planning, use conventional, reversible defaults for ordinary details and record each assumption; material ambiguity stops affected work as needs-review with a concrete question. Independent authorized planning may continue.
|
|
8
|
+
2. Write `docs/specs/<loop-id>/meta.md` and self-contained task contracts per [package-format.md](package-format.md). Preserve an input document unchanged and carry every substantive requirement into the package. Use this execution policy in the package: for ordinary details within authorized scope, follow established project conventions, otherwise the narrowest literal or conventional reading consistent with the request; choices must be low-consequence and reversible, recorded with their reasons. Material ambiguity means competing intended outcomes, unresolved persistent data/state semantics or public surface beyond the request; stop affected work as needs-review with a concrete question. Independent authorized work may continue. Never omit requested work or weaken acceptance. Set delivery from explicit restrictions or the project completion policy; without one GitHub origin, record local-only as an assumption, or ask when the user explicitly requested a PR/merge.
|
|
9
|
+
3. Run `python3 "<DEVLYN_SKILL_DIR>/scripts/queue.py" check '<absolute meta.md>'` and repair until `VALID`. Plan stops here: report the path, tasks, dependencies and assumptions.
|
|
10
|
+
4. When add is authorized, run `python3 "<DEVLYN_SKILL_DIR>/scripts/queue.py" add '<absolute meta.md>'`. It alone writes queue state, captures without committing on the checked-out branch and removes matching untracked package copies. Report the capture ref, its `git show` command and cleanup diagnostics. Revisions need a new loop id. A legacy pending row is materialized with `--materialize <line>` and its verbatim intent in the package.
|
|
11
|
+
|
|
12
|
+
Report capture and stop. A separate authorized execution session owns drain.
|