@loop-lang/loop 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/assets/AGENTS.md +255 -0
- package/assets/examples/fix_test.loop +6 -0
- package/assets/skill/SKILL.md +237 -0
- package/package.json +18 -0
- package/src/cli.mjs +84 -0
- package/src/init.mjs +100 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @loop-lang/loop
|
|
2
|
+
|
|
3
|
+
Install **Loop** — a small natural-language DSL for self-correcting, human-gated coding loops — into any repo, so **Claude Code or any agent** can author and run `.loop` files.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx @loop-lang/loop init
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
That scaffolds, into the current repo:
|
|
10
|
+
|
|
11
|
+
- **`AGENTS.md`** — the full Loop language reference. Any agent that opens the repo (Claude Code, Cursor, Copilot, Codex…) now knows how to write a `.loop`.
|
|
12
|
+
- **`.claude/skills/loop`** — the Claude Code `/loop` skill (author + run loops natively in a chat).
|
|
13
|
+
- **`examples/fix_test.loop`** — a starter loop to run.
|
|
14
|
+
|
|
15
|
+
### Options
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
loop init [--dir <path>] [--global] [--no-skill] [--no-example]
|
|
19
|
+
[--claude-md] [--cursor] [--copilot] [--all-agents] [--force]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- `--global` — install the skill into `~/.claude/skills` instead of the repo.
|
|
23
|
+
- `--all-agents` — also drop memory pointers for Cursor + Copilot (and `CLAUDE.md`).
|
|
24
|
+
- `--force` — overwrite an existing skill / example.
|
|
25
|
+
|
|
26
|
+
Re-running `init` is safe: the `AGENTS.md` block is managed between markers and updated in place, not duplicated.
|
|
27
|
+
|
|
28
|
+
### After install
|
|
29
|
+
|
|
30
|
+
- **Claude Code:** open a chat in the repo → `/loop run examples/fix_test.loop`, or just describe the work and the agent writes the `.loop`.
|
|
31
|
+
- **Any agent:** it reads `AGENTS.md` and can author + run loops the same way.
|
|
32
|
+
- **Headless:** install the full runtime for `loop run <file>`.
|
|
33
|
+
|
|
34
|
+
Learn the language: the [tutorial](https://github.com/tickets-forge-dev/loop-lang), the keyword reference, and the playable Loop Lab.
|
package/assets/AGENTS.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# AGENTS.md — authoring Loop (`.loop`) flows
|
|
2
|
+
|
|
3
|
+
This file teaches an AI assistant (Claude Code, Copilot, Cursor, etc.) how to write
|
|
4
|
+
**Loop** flows. When a user asks you to design a staged, self-correcting, or human-gated
|
|
5
|
+
coding workflow — "set up a loop to fix X", "turn this epic into a pipeline", "automate
|
|
6
|
+
this multi-step task" — author a `.loop` file using the grammar below, then let the user
|
|
7
|
+
run it with `loop run file.loop`.
|
|
8
|
+
|
|
9
|
+
Loop is a small natural-language DSL. A `.loop` file describes the *movement* of an AI
|
|
10
|
+
coding loop: its objective, the context it may read, the actions it's allowed, how it
|
|
11
|
+
verifies itself, when it stops, and where a human steps in. The five knobs —
|
|
12
|
+
**objective, context, actions, verification, stopping rules** — are first-class instead
|
|
13
|
+
of buried in a prompt.
|
|
14
|
+
|
|
15
|
+
## When to write a `.loop`
|
|
16
|
+
|
|
17
|
+
Write one when the work is a *repeatable, verifiable* loop or a sequence of them:
|
|
18
|
+
bug fixes with a test, refactors gated by a check, an epic broken into stories, a
|
|
19
|
+
migration with a verification step. Don't write one for a one-off question or a trivial
|
|
20
|
+
edit — just do those directly.
|
|
21
|
+
|
|
22
|
+
**Interview the user before writing it.** Walk the five decisions, asking the
|
|
23
|
+
high-leverage questions and offering defaults for the rest: (1) the **goal**;
|
|
24
|
+
(2) the **`done when`** check (test / command / scan finds-nothing / human);
|
|
25
|
+
(3) **`look at`** context; (4) the **action policy** (what's risky enough to gate);
|
|
26
|
+
(5) **stopping** (reflect on failure + an `after N tries` guard). Then the
|
|
27
|
+
**human gates** and the **git strategy** (default: branch + commit when done,
|
|
28
|
+
never push to `main`; ask if they want a PR or a worktree). Offer the defaults
|
|
29
|
+
inline so a confident user can accept everything at once.
|
|
30
|
+
|
|
31
|
+
## Vocabulary (the whole language)
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
loop "<name>": a self-correcting loop
|
|
35
|
+
pipeline "<name>": a sequence of stages (an epic)
|
|
36
|
+
stage "<name>": one stage of a pipeline (its body is a loop; a story)
|
|
37
|
+
flow "<name>": a chain of loop files (each step runs a whole .loop file)
|
|
38
|
+
run "<file>": first step — runs the file; its text result is passed forward
|
|
39
|
+
then run "<file>": subsequent step — receives the previous result as context
|
|
40
|
+
a human approves first (optional per-step human gate before the step runs)
|
|
41
|
+
with the result of <name> (reference a named step's output instead of auto-carry)
|
|
42
|
+
for each <var> in "<file>": iterate items from a .yaml or .md file; run the template once per item
|
|
43
|
+
run "<template>": template receives the item text as context; fail → ask continue/stop
|
|
44
|
+
|
|
45
|
+
goal: <text> what "done" means, in plain language
|
|
46
|
+
done when <predicate> how the loop verifies itself (see Predicates)
|
|
47
|
+
look at: <files>, and the last failure context the agent reads before acting (items are file paths or plain-language descriptions the agent resolves to files)
|
|
48
|
+
allow edits automatically, but ask me before <classes> action policy
|
|
49
|
+
each cycle: plan, then act, then observe the repeated steps (any subset, in order)
|
|
50
|
+
also: <pass>, <pass> extra finishing passes run after the goal is met
|
|
51
|
+
reflect turn a failure into context for the next plan (the back-edge)
|
|
52
|
+
|
|
53
|
+
when it passes and the goal is met: stop
|
|
54
|
+
when it fails: reflect on <focus>, then plan again
|
|
55
|
+
when blocked: ask a human
|
|
56
|
+
after <N> tries: stop and warn "<message>"
|
|
57
|
+
|
|
58
|
+
a human approves the plan first (human authors/approves the plan before acting)
|
|
59
|
+
a human reviews before stopping (human judges the result before the loop stops)
|
|
60
|
+
a human approves before <action> (a blocking gate before a stage, e.g. deploy)
|
|
61
|
+
|
|
62
|
+
plan from the archon project "<name>" (source the plan from Archon instead of generating)
|
|
63
|
+
|
|
64
|
+
use the <method> method schedule: <when> runner: <agent> target: <dir> (config tier)
|
|
65
|
+
models: fast <model>, strong <model> model tiering: plan/reflect/also→fast, act→strong (cascades; override e.g. `act fast`, `all strong`)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Predicates (`done when …`)
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
done when the test "billing.spec.ts::apostrophe" passes # a named test
|
|
72
|
+
done when "pnpm test" passes # a shell command, exit 0
|
|
73
|
+
done when "semgrep --severity=high" finds nothing # a shell command, empty output
|
|
74
|
+
done when a human confirms "looks right at 375px" # a human check
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The command in a predicate runs in the user's shell with their privileges (like an npm
|
|
78
|
+
script). It IS meant to be a real command. Prefer a fast, deterministic check.
|
|
79
|
+
|
|
80
|
+
### `flow` — chaining loops across files
|
|
81
|
+
|
|
82
|
+
A `flow` sequences multiple `.loop` files. Each step runs the whole file (plan→act→observe
|
|
83
|
+
cycle) and passes its text result forward as context for the next step. The chain is
|
|
84
|
+
fail-fast: a step that ends unsatisfied stops the rest.
|
|
85
|
+
|
|
86
|
+
```loop
|
|
87
|
+
flow "ship":
|
|
88
|
+
run "build.loop"
|
|
89
|
+
then run "test.loop"
|
|
90
|
+
then run "deploy.loop":
|
|
91
|
+
a human approves first
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- `run "<file>"` — first step; the file path is relative to the flow file.
|
|
95
|
+
- `then run "<file>"` — subsequent steps; automatically receive the previous step's text summary.
|
|
96
|
+
- `a human approves first` — optional per-step gate; blocks until approved.
|
|
97
|
+
- `with the result of <name>` — reference a named step's output explicitly instead of auto-carry.
|
|
98
|
+
|
|
99
|
+
### `for each` — iterate a plan, run a template per item
|
|
100
|
+
|
|
101
|
+
Inside a `flow`, `for each` reads a list from a YAML or Markdown file and runs a template
|
|
102
|
+
once per entry. The entry's text becomes the template's context (what to build).
|
|
103
|
+
|
|
104
|
+
```loop
|
|
105
|
+
flow "deliver":
|
|
106
|
+
for each item in "plan.yaml":
|
|
107
|
+
run "item-template.loop"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- `for each <var> in "<file>":` — source must be a `.yaml` file (a list or a single-key
|
|
111
|
+
list like `items:`) or a `.md` file (splits on `## ` sections).
|
|
112
|
+
- `run "<template>"` — the template runs once per item; the item text arrives as context.
|
|
113
|
+
- A failed item pauses the flow and asks whether to continue with the next item or stop.
|
|
114
|
+
- Method-neutral: works with any checklist, not only BMAD. See `examples/foreach/` for a
|
|
115
|
+
generic bundle and `examples/bmad/atoz/` for BMAD as one example method.
|
|
116
|
+
|
|
117
|
+
## Rules
|
|
118
|
+
|
|
119
|
+
- **Indentation matters.** `loop` / `pipeline` / `flow` at column 0; their body indented
|
|
120
|
+
two spaces; a `stage`'s body indented under the stage.
|
|
121
|
+
- A `loop` needs a `goal`. A `pipeline` needs at least one `stage`.
|
|
122
|
+
- **An epic → a `pipeline`; each story → a `stage`.** Stages run in order; a failing
|
|
123
|
+
stage halts the rest.
|
|
124
|
+
- **Scope each loop with `look at:`** so the agent follows the existing architecture and
|
|
125
|
+
makes the smallest change, instead of writing greenfield code. Items can be exact file
|
|
126
|
+
paths or plain-language descriptions (e.g. `the billing form`) — the agent resolves
|
|
127
|
+
descriptions to the actual files before planning.
|
|
128
|
+
- **Put human gates on risky work** — payments, migrations, deploys, anything
|
|
129
|
+
irreversible. Use `ask me before …` for action policy, `a human approves before …`
|
|
130
|
+
for a hard stage gate.
|
|
131
|
+
- Output only valid `.loop` syntax. Comments start with `#`.
|
|
132
|
+
|
|
133
|
+
## Example — a single loop
|
|
134
|
+
|
|
135
|
+
```loop
|
|
136
|
+
loop "fix billing apostrophe bug":
|
|
137
|
+
goal: settings save when the company name has an apostrophe
|
|
138
|
+
done when the test "billing.spec.ts::apostrophe" passes
|
|
139
|
+
|
|
140
|
+
look at: billing/form.tsx, api/settings.ts, schema/settings.ts, and the last failure
|
|
141
|
+
allow edits automatically, but ask me before migrations or pushes
|
|
142
|
+
|
|
143
|
+
each cycle: plan, then act, then observe
|
|
144
|
+
when it passes and the goal is met: stop
|
|
145
|
+
when it fails: reflect on which layer broke, then plan again
|
|
146
|
+
when blocked: ask a human
|
|
147
|
+
also: polish the code, run a security check
|
|
148
|
+
after 6 tries: stop and warn "thrashing"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Example — an epic with stories
|
|
152
|
+
|
|
153
|
+
```loop
|
|
154
|
+
pipeline "epic: checkout v2":
|
|
155
|
+
|
|
156
|
+
stage "story: cart totals":
|
|
157
|
+
goal: cart shows correct totals with tax
|
|
158
|
+
look at: src/cart/, src/tax/
|
|
159
|
+
done when "pnpm test cart" passes
|
|
160
|
+
each cycle: plan, then act, then observe
|
|
161
|
+
when it fails: reflect, then plan again
|
|
162
|
+
|
|
163
|
+
stage "story: checkout submit":
|
|
164
|
+
goal: order submits and payment is captured
|
|
165
|
+
a human approves before charging the card
|
|
166
|
+
done when "pnpm test checkout" passes
|
|
167
|
+
each cycle: act, then observe
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Git strategy
|
|
171
|
+
|
|
172
|
+
A `git:` block sets the version-control strategy for the whole file (config tier, before
|
|
173
|
+
any definition) or for a single loop (inside the loop body).
|
|
174
|
+
|
|
175
|
+
**Built-in default (no `git:` block):** work on a branch, commit when the goal is met,
|
|
176
|
+
no push. This applies whenever no git block is present at any level.
|
|
177
|
+
|
|
178
|
+
### Line forms
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
work in place # edit the current branch as-is
|
|
182
|
+
work on a branch # create / switch to a feature branch (default)
|
|
183
|
+
work on a branch "my-feature" # explicit branch name
|
|
184
|
+
work in a worktree # isolated git worktree
|
|
185
|
+
work in a worktree "my-worktree" # named worktree
|
|
186
|
+
|
|
187
|
+
commit when the goal is met # one commit on success (default)
|
|
188
|
+
commit each cycle # commit after every cycle
|
|
189
|
+
commit each story # commit after each stage
|
|
190
|
+
commit never / do not commit # no automatic commits
|
|
191
|
+
|
|
192
|
+
push when done # push the branch on completion
|
|
193
|
+
do not push # no push (default)
|
|
194
|
+
|
|
195
|
+
open a pull request # open a PR after pushing
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Cascade (lowest wins)
|
|
199
|
+
|
|
200
|
+
1. Built-in default — branch + commit-when-done, no push.
|
|
201
|
+
2. File-level `git:` block — applies to all loops in the file.
|
|
202
|
+
3. Per-loop `git:` block — refines commit cadence for that loop only.
|
|
203
|
+
|
|
204
|
+
A `use the <method>` preset may carry a `git:` block at file level; the file's own block
|
|
205
|
+
overrides it.
|
|
206
|
+
|
|
207
|
+
### Always-on safety
|
|
208
|
+
|
|
209
|
+
- **Never push to `main` or `master`.** This is unconditional — no `git:` block can
|
|
210
|
+
override it. A `push when done` directive with the current branch being protected is an
|
|
211
|
+
error that surfaces before the loop runs.
|
|
212
|
+
- **`work in place` + `push when done` on a protected branch** is also an up-front error.
|
|
213
|
+
|
|
214
|
+
### Example
|
|
215
|
+
|
|
216
|
+
```loop
|
|
217
|
+
git:
|
|
218
|
+
work on a branch
|
|
219
|
+
commit when the goal is met
|
|
220
|
+
push when done
|
|
221
|
+
open a pull request
|
|
222
|
+
|
|
223
|
+
loop "add a healthcheck endpoint":
|
|
224
|
+
goal: GET /healthz returns 200 with a JSON status
|
|
225
|
+
done when "pnpm test health" passes
|
|
226
|
+
look at: the http server and the routes module, and the last failure
|
|
227
|
+
each cycle: plan, then act, then observe
|
|
228
|
+
when it fails: reflect, then plan again
|
|
229
|
+
after 6 tries: stop and warn "healthcheck stuck"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Show the flow — every time it changes
|
|
233
|
+
|
|
234
|
+
Whenever you create or edit a `.loop`, print its flow so the user sees the shape.
|
|
235
|
+
Run `loop show file.loop`, or render the compact ASCII yourself: the cycle
|
|
236
|
+
(`plan → act → observe`), the `↺` reflect back-edge, the `✓ done when` check, the
|
|
237
|
+
`⛔` thrash guard, and any `👤` gates. For a pipeline, list stages in order; for a
|
|
238
|
+
flow, show the file chain. `loop ls` lists every loop in the repo.
|
|
239
|
+
|
|
240
|
+
## Running what you wrote
|
|
241
|
+
|
|
242
|
+
- `loop run file.loop` — execute it on Claude Code (plan/act/observe, reflect on failure,
|
|
243
|
+
verify with `done when`, pause at human gates).
|
|
244
|
+
- `loop show file.loop` — print the loop's flow as compact ASCII (and `loop ls` to list them).
|
|
245
|
+
- `loop viz file.loop` — open a visual HTML schematic of the flow.
|
|
246
|
+
- `loop export file.loop` — emit an Archon workflow YAML (optional interop).
|
|
247
|
+
|
|
248
|
+
## Authoring checklist
|
|
249
|
+
|
|
250
|
+
1. One coherent objective per `loop`; one story per `stage`.
|
|
251
|
+
2. A real, fast `done when` predicate — never claim done without a check.
|
|
252
|
+
3. `look at:` the relevant files so the agent stays inside the architecture.
|
|
253
|
+
4. A `when it fails: reflect, then plan again` so the loop self-corrects.
|
|
254
|
+
5. An `after N tries` thrash guard so it can't spin forever.
|
|
255
|
+
6. Human gates on anything irreversible.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loop
|
|
3
|
+
description: Create and run Loop (.loop) flows — a natural-language DSL for loop engineering. Use when the user wants to author, write, run, or execute a self-correcting or human-gated AI coding loop, turn an epic into a pipeline, fix a bug "as a loop", or mentions a .loop file or "loop engineering".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Loop
|
|
7
|
+
|
|
8
|
+
Loop is a small natural-language DSL for **loop engineering**: a `.loop` file describes a
|
|
9
|
+
staged, self-correcting, human-gated coding workflow — its objective, the context it may
|
|
10
|
+
read, the actions it may take, how it verifies itself, when it stops, and where a human
|
|
11
|
+
steps in.
|
|
12
|
+
|
|
13
|
+
You do two things with this skill: **create** `.loop` files, and **run** them — natively,
|
|
14
|
+
in this conversation, so the user watches every step and answers gates right here.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## The language
|
|
19
|
+
|
|
20
|
+
Vocabulary (the whole DSL):
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
loop "<name>": a self-correcting loop
|
|
24
|
+
pipeline "<name>": a sequence of stages (an epic)
|
|
25
|
+
stage "<name>": one stage of a pipeline (its body is a loop; a story)
|
|
26
|
+
|
|
27
|
+
flow "<name>": a chain of whole .loop files, run in order (handoff is text)
|
|
28
|
+
run "<file.loop>" a flow step: run that whole file
|
|
29
|
+
then run "<file.loop>" the next step; the prior file's summary carries forward as context
|
|
30
|
+
... with the result of <step> pull the handoff from a named earlier step instead of the previous
|
|
31
|
+
for each <var> in "<file>": run the child template once per item in <file>
|
|
32
|
+
run "<template.loop>"
|
|
33
|
+
|
|
34
|
+
# `for each` source: .yaml/.yml (a list, or items under a single key) or .md (each `## ` section).
|
|
35
|
+
# Each item's text becomes the template's context for that run.
|
|
36
|
+
|
|
37
|
+
goal: <text> what "done" means, in plain language
|
|
38
|
+
done when <predicate> how the loop verifies itself
|
|
39
|
+
look at: <files>, and the last failure context to read before acting
|
|
40
|
+
allow edits automatically, but ask me before <classes> action policy
|
|
41
|
+
each cycle: plan, then act, then observe the repeated steps (any subset, in order)
|
|
42
|
+
also: <pass>, <pass> extra finishing passes after the goal is met
|
|
43
|
+
when it fails: reflect on <focus>, then plan again
|
|
44
|
+
when it passes and the goal is met: stop
|
|
45
|
+
when blocked: ask a human
|
|
46
|
+
after <N> tries: stop and warn "<message>" thrash guard
|
|
47
|
+
a human approves the plan first human plan gate
|
|
48
|
+
a human reviews before stopping human review gate
|
|
49
|
+
a human approves before <action> stage gate (in a stage)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Predicates:
|
|
53
|
+
```
|
|
54
|
+
done when the test "file.spec.ts::name" passes
|
|
55
|
+
done when "pnpm test" passes # shell command, exit 0
|
|
56
|
+
done when "semgrep --severity=high" finds nothing # empty output
|
|
57
|
+
done when a human confirms "looks right"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Rules: indentation is structural (two spaces); `loop`/`pipeline` at column 0; comments
|
|
61
|
+
start with `#`. An epic → a `pipeline`; each story → a `stage`.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Creating a .loop — interview the user first
|
|
66
|
+
|
|
67
|
+
Don't silently guess the loop. **Walk the five decisions with the user**, asking
|
|
68
|
+
the high-leverage questions out loud and offering a default for the rest, so a
|
|
69
|
+
confident user can accept everything in one reply. Ask one topic at a time:
|
|
70
|
+
|
|
71
|
+
1. **Goal (objective)** — "What's the goal — what does *done* look like?"
|
|
72
|
+
Required; never guess this one.
|
|
73
|
+
2. **Verification — `done when`** — "How do we know it's done: a test passing, a
|
|
74
|
+
shell command passing, a scan that must *find nothing*, or a human confirming?"
|
|
75
|
+
The most important answer — always ask.
|
|
76
|
+
3. **Context — `look at`** — "What should it read first?" Offer to infer the files
|
|
77
|
+
from the repo; append `and the last failure`.
|
|
78
|
+
4. **Actions — policy** — "Anything risky to gate — migrations, pushes, deploys?"
|
|
79
|
+
Default offered: edits automatically, nothing else gated.
|
|
80
|
+
5. **Stopping — transitions + guard** — confirm the default (reflect on failure,
|
|
81
|
+
`after N tries: stop and warn`); ask for N only if they care.
|
|
82
|
+
|
|
83
|
+
Then two more that shape the run:
|
|
84
|
+
|
|
85
|
+
6. **Human gates** — "Approve the plan before any work? Review before it stops?"
|
|
86
|
+
Default: none unless the work is risky.
|
|
87
|
+
7. **Git strategy** — state the safe default ("work on a branch, commit when the
|
|
88
|
+
goal is met, never push to `main`") and ask if they want a PR, a worktree, or
|
|
89
|
+
to work in place.
|
|
90
|
+
|
|
91
|
+
Offer the defaults inline (e.g. *"I'll gate nothing, add a 6-try guard, and work
|
|
92
|
+
on a branch — ok?"*) so the whole interview can be one exchange. Then **write the
|
|
93
|
+
`.loop`**, scoping it with `look at:` and always giving it a real `done when` and a
|
|
94
|
+
thrash guard. Finally **print its flow** (below) and offer to run it.
|
|
95
|
+
|
|
96
|
+
## Show the flow — every time it changes
|
|
97
|
+
|
|
98
|
+
Whenever you **create or edit** a `.loop`, immediately print its flow so the user
|
|
99
|
+
watches the shape evolve. If the `loop` CLI is installed, run `loop show <file>`.
|
|
100
|
+
Otherwise render the same compact ASCII yourself:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
loop "fix test"
|
|
104
|
+
↻ plan → act → observe (each cycle)
|
|
105
|
+
↺ on fail: reflect → plan (the back-edge)
|
|
106
|
+
✓ done when: test "checkout.spec.ts::tax"
|
|
107
|
+
⛔ guard: after 6 tries → stop & warn "stuck"
|
|
108
|
+
· goal: the tax line is correct
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
For a **pipeline**, list the stages in order and mark 👤 gates; for a **flow**,
|
|
112
|
+
show the file chain (`a.loop → b.loop → …`). `loop ls` lists every loop in the repo.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Running a .loop (in this session)
|
|
117
|
+
|
|
118
|
+
Prefer running it **yourself, here** — that way the whole loop is visible in the
|
|
119
|
+
conversation and the user answers gates inline. (Only shell out to the `loop run` CLI if
|
|
120
|
+
the user explicitly asks for the headless runner.)
|
|
121
|
+
|
|
122
|
+
1. **Read the file.** (Or, if the `loop` CLI is installed: `loop parse <file> --json` to
|
|
123
|
+
get the structured spec.)
|
|
124
|
+
2. **Execute the loop's semantics, narrating each step:**
|
|
125
|
+
- **plan** — inspect the `look at:` files; decide the smallest change toward the goal.
|
|
126
|
+
- **act** — make the edits. Honor the policy: for `ask me before <X>`, ask the user
|
|
127
|
+
before doing X (migrations, pushes, etc.); auto classes you may do directly.
|
|
128
|
+
- **observe** — run the `done when` command (or named test) and read pass/fail.
|
|
129
|
+
- on **fail** → **reflect** on why (use the failure output), then **plan again** (the
|
|
130
|
+
back-edge). Repeat.
|
|
131
|
+
- **stop** when `done when` passes, or after the thrash guard's N tries (state the
|
|
132
|
+
warning), or if genuinely blocked (ask the user).
|
|
133
|
+
- **human gates:** `a human approves the plan first` → present the plan and wait for
|
|
134
|
+
approval before acting. `a human reviews before stopping` → ask before declaring done.
|
|
135
|
+
`a human approves before <X>` → ask before that stage runs.
|
|
136
|
+
- **pipelines:** run stages in order; if a stage can't be satisfied, halt the rest.
|
|
137
|
+
- **flows:** run each referenced file in order, the *whole* file. After each, carry a
|
|
138
|
+
short text summary of how it went forward as context for the next file. If a file
|
|
139
|
+
isn't satisfied, halt the rest (fail-fast). `with the result of <step>` redirects
|
|
140
|
+
which earlier summary to carry.
|
|
141
|
+
- **for each `<var>` in `<file>`:** read the source file and split it into items
|
|
142
|
+
(`.yaml` list entries, or `.md` `## ` sections). Run the template loop once per item,
|
|
143
|
+
giving the template that item's text as its context. If an item's checklist fails,
|
|
144
|
+
pause and ask the user: continue to the next item, or stop the whole flow? Continuing
|
|
145
|
+
accepts that item and proceeds; reaching the end means done.
|
|
146
|
+
- **`also:`** finishing passes run only after the goal is met (skip them if it failed).
|
|
147
|
+
3. **Report a concise trace** — one line per cycle step (plan / act / observe = PASS|fail /
|
|
148
|
+
reflect / stop), and the final outcome.
|
|
149
|
+
|
|
150
|
+
Because you are the one running it, the user sees the real work — file reads, edits,
|
|
151
|
+
command output — as part of this session, and answers any gate right in the chat.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Interactive discovery (the front of an A-to-Z flow)
|
|
156
|
+
|
|
157
|
+
A discovery/planning step is just a loop whose goal is to produce a planning artifact (a
|
|
158
|
+
spec, PRD, or plan file). Run it as a real conversation:
|
|
159
|
+
|
|
160
|
+
- Interview the user — ask the questions the method calls for — across as many turns as it
|
|
161
|
+
takes. You're naturally suspended between their answers, so a long session (even an hour)
|
|
162
|
+
is fine; there's nothing to "wait" on.
|
|
163
|
+
- The step is **done when its `done when` artifact check passes** — e.g. the plan file
|
|
164
|
+
exists and has the required sections — NOT when the conversation goes quiet. Re-check the
|
|
165
|
+
artifact; when it validates, move on. (The questions come from the loop's goal/context —
|
|
166
|
+
i.e. the method — not from this skill.)
|
|
167
|
+
|
|
168
|
+
So a full method, end to end, is a `flow`:
|
|
169
|
+
|
|
170
|
+
flow "deliver: <feature>":
|
|
171
|
+
run "discover.loop" # conversation: interview → write the plan file
|
|
172
|
+
then run "design.loop" # design from it; a human approves
|
|
173
|
+
then for each item in "plan.yaml":
|
|
174
|
+
run "item-template.loop" # the per-item checklist, once per item
|
|
175
|
+
|
|
176
|
+
`discover.loop` ends via e.g. `done when "<validator> plan.yaml"`; each item then runs the
|
|
177
|
+
same checklist. (Name things to taste — a BMAD setup might use `story`/`sprint.yaml`.)
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Git strategy
|
|
182
|
+
|
|
183
|
+
A `git:` block sets the version-control strategy for the whole file (config tier) or for
|
|
184
|
+
a single loop. When there is no `git:` block at all the built-in default applies:
|
|
185
|
+
**work on a branch, commit when the goal is met, no push**.
|
|
186
|
+
|
|
187
|
+
### Line forms
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
work in place # edit the current branch as-is
|
|
191
|
+
work on a branch # create / switch to a feature branch (default)
|
|
192
|
+
work on a branch "my-feature" # name the branch explicitly
|
|
193
|
+
work in a worktree # isolated git worktree
|
|
194
|
+
work in a worktree "my-worktree" # named worktree
|
|
195
|
+
|
|
196
|
+
commit when the goal is met # one commit when done (default)
|
|
197
|
+
commit each cycle # commit after every plan→act→observe cycle
|
|
198
|
+
commit each story # commit after each stage in a pipeline
|
|
199
|
+
commit never / do not commit # no automatic commits
|
|
200
|
+
|
|
201
|
+
push when done # push the branch when the loop finishes
|
|
202
|
+
do not push # no push (default)
|
|
203
|
+
|
|
204
|
+
open a pull request # open a PR after pushing (requires push when done)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Cascade
|
|
208
|
+
|
|
209
|
+
Settings resolve in three layers, each refining the one above:
|
|
210
|
+
|
|
211
|
+
1. **Built-in default** — branch + commit-when-done, no push.
|
|
212
|
+
2. **File-level `git:` block** — placed at the top of the `.loop` file, before any loop definition; applies to every loop in the file.
|
|
213
|
+
3. **Per-loop `git:` block** — placed inside a single `loop` body; refines the commit cadence for that loop only.
|
|
214
|
+
|
|
215
|
+
A `use the <method>` preset may also carry a `git:` block; it applies at the file level and is then overridden by the file's own `git:` block if present.
|
|
216
|
+
|
|
217
|
+
### Always-on safety
|
|
218
|
+
|
|
219
|
+
Two protections are unconditional and cannot be overridden by any `git:` block:
|
|
220
|
+
|
|
221
|
+
- **Never push to `main` or `master`.** If the current branch is `main` or `master` (or a branch whose name matches the protected set), any `push when done` directive is an error that surfaces *before the loop runs*, not after.
|
|
222
|
+
- **`work in place` + `push when done` on a protected branch** is also rejected up front.
|
|
223
|
+
|
|
224
|
+
### In-chat runner note
|
|
225
|
+
|
|
226
|
+
When you run a loop *inside this conversation* (the `/loop` skill), you are the git
|
|
227
|
+
operator — you execute plan/act/observe yourself. Before acting, check the `git:` block
|
|
228
|
+
and honor the policy: if `push when done` is set and the current branch is `main` or
|
|
229
|
+
`master`, refuse with a clear message rather than pushing.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Reference
|
|
234
|
+
|
|
235
|
+
The full language reference is in `AGENTS.md` and `docs/MANUAL.md` of the loop-lang repo;
|
|
236
|
+
the CLI (`loop run|viz|export|parse`) and the VSCode extension are alternative ways to run
|
|
237
|
+
the same `.loop` files.
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@loop-lang/loop",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install Loop (.loop) into any repo so Claude Code — or any agent — can author and run self-correcting, human-gated coding loops.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"publishConfig": { "access": "public" },
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": { "loop": "src/cli.mjs" },
|
|
9
|
+
"files": ["src", "assets", "README.md"],
|
|
10
|
+
"engines": { "node": ">=18" },
|
|
11
|
+
"keywords": ["loop", "agent", "claude", "ai", "workflow", "dsl", "loop-engineering"],
|
|
12
|
+
"repository": { "type": "git", "url": "https://github.com/tickets-forge-dev/loop-lang.git", "directory": "packages/cli" },
|
|
13
|
+
"scripts": {
|
|
14
|
+
"sync-assets": "node scripts/sync-assets.mjs",
|
|
15
|
+
"prepack": "node scripts/sync-assets.mjs",
|
|
16
|
+
"test": "node scripts/sync-assets.mjs && node --test test/*.test.mjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @loop-lang/loop — the installer CLI. `loop init` drops Loop into a repo so any
|
|
3
|
+
// agent can author + run .loop files. Running loops happens in your agent (the
|
|
4
|
+
// /loop skill) or headless via the full @loop/runtime CLI.
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { init } from "./init.mjs";
|
|
8
|
+
|
|
9
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const ASSETS = join(here, "..", "assets");
|
|
11
|
+
|
|
12
|
+
const HELP = `loop — install Loop into your repo so any agent can author + run .loop files
|
|
13
|
+
|
|
14
|
+
usage:
|
|
15
|
+
loop init [options] scaffold Loop into the current repo
|
|
16
|
+
loop help show this
|
|
17
|
+
|
|
18
|
+
init options:
|
|
19
|
+
--dir <path> install into <path> (default: current directory)
|
|
20
|
+
--global install the /loop skill into ~/.claude/skills (not the repo)
|
|
21
|
+
--no-skill don't install the Claude Code /loop skill
|
|
22
|
+
--no-example don't write examples/fix_test.loop
|
|
23
|
+
--claude-md also write a CLAUDE.md pointer
|
|
24
|
+
--cursor also write .cursor/rules/loop.md
|
|
25
|
+
--copilot also write .github/copilot-instructions.md
|
|
26
|
+
--all-agents CLAUDE.md + Cursor + Copilot pointers
|
|
27
|
+
--force overwrite the skill / example if they already exist
|
|
28
|
+
|
|
29
|
+
after init:
|
|
30
|
+
• Claude Code: open a chat in the repo and say /loop run examples/fix_test.loop
|
|
31
|
+
• any agent: it reads AGENTS.md and can author + run .loop files
|
|
32
|
+
• headless: install @loop/runtime for loop run <file>`;
|
|
33
|
+
|
|
34
|
+
function flag(argv, name) { return argv.includes(name); }
|
|
35
|
+
function opt(argv, name) { const i = argv.indexOf(name); return i >= 0 ? argv[i + 1] : undefined; }
|
|
36
|
+
|
|
37
|
+
async function main(argv) {
|
|
38
|
+
const cmd = argv[0];
|
|
39
|
+
|
|
40
|
+
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
41
|
+
console.log(HELP);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (cmd === "init") {
|
|
46
|
+
const agents = [];
|
|
47
|
+
if (flag(argv, "--all-agents")) agents.push("claude", "cursor", "copilot");
|
|
48
|
+
if (flag(argv, "--claude-md") && !agents.includes("claude")) agents.push("claude");
|
|
49
|
+
if (flag(argv, "--cursor") && !agents.includes("cursor")) agents.push("cursor");
|
|
50
|
+
if (flag(argv, "--copilot") && !agents.includes("copilot")) agents.push("copilot");
|
|
51
|
+
|
|
52
|
+
const targetDir = resolve(process.cwd(), opt(argv, "--dir") ?? ".");
|
|
53
|
+
const skill = flag(argv, "--no-skill") ? "none" : flag(argv, "--global") ? "global" : "local";
|
|
54
|
+
|
|
55
|
+
const { steps } = await init(targetDir, {
|
|
56
|
+
skill,
|
|
57
|
+
agents,
|
|
58
|
+
example: !flag(argv, "--no-example"),
|
|
59
|
+
force: flag(argv, "--force"),
|
|
60
|
+
}, ASSETS);
|
|
61
|
+
|
|
62
|
+
console.log(`\n Loop installed into ${targetDir}\n`);
|
|
63
|
+
for (const s of steps) console.log(` ✓ ${s}`);
|
|
64
|
+
console.log(`\n Next:`);
|
|
65
|
+
console.log(` • In a Claude Code chat here: /loop run examples/fix_test.loop`);
|
|
66
|
+
console.log(` • Or describe the work and the agent writes the .loop for you.`);
|
|
67
|
+
console.log(` • Any other agent reads AGENTS.md and can do the same.\n`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (["run", "parse", "export", "viz", "show", "ls"].includes(cmd)) {
|
|
72
|
+
console.error(`\`loop ${cmd}\` runs in your agent (the /loop skill) or via the full runtime CLI (@loop/runtime).\nThis package installs Loop — try \`loop init\`. See \`loop help\`.`);
|
|
73
|
+
process.exit(2);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.error(`unknown command: ${cmd}\n`);
|
|
77
|
+
console.log(HELP);
|
|
78
|
+
process.exit(2);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
82
|
+
console.error(String(err?.message ?? err));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|
package/src/init.mjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// `loop init` — install Loop into a repo so any agent can author + run .loop files.
|
|
2
|
+
// Pure-ish: takes a target dir + options + the assets dir; returns the steps taken.
|
|
3
|
+
// No external deps (node built-ins only) so the published package runs under npx as-is.
|
|
4
|
+
import { readFile, writeFile, mkdir, cp, access } from "node:fs/promises";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join, dirname } from "node:path";
|
|
7
|
+
|
|
8
|
+
const MARK_START = "<!-- loop:start (managed by `loop init` — edits between the markers are overwritten) -->";
|
|
9
|
+
const MARK_END = "<!-- loop:end -->";
|
|
10
|
+
|
|
11
|
+
/** Short pointer dropped into an agent's memory file so it knows Loop lives here. */
|
|
12
|
+
export function pointer({ skill }) {
|
|
13
|
+
const run = skill
|
|
14
|
+
? "run it — in Claude Code via the `/loop` skill (installed at `.claude/skills/loop`), or headless with `loop run <file>`"
|
|
15
|
+
: "run it headless with `loop run <file>`";
|
|
16
|
+
return [
|
|
17
|
+
"## Loop (`.loop`)",
|
|
18
|
+
"",
|
|
19
|
+
"This repo uses **Loop** — a small natural-language DSL for self-correcting, human-gated coding workflows.",
|
|
20
|
+
"When the user asks to set up a loop, turn an epic into a pipeline, or automate a multi-step task, **author a `.loop` file** using the grammar in [`AGENTS.md`](./AGENTS.md), then " + run + ".",
|
|
21
|
+
"Every time you create or change a `.loop`, print its flow so the user can see the shape.",
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const exists = (p) => access(p).then(() => true, () => false);
|
|
26
|
+
|
|
27
|
+
/** Write `body` into `file`, replacing a prior `loop init` block if present, else appending/creating. */
|
|
28
|
+
async function mergeMarkered(file, body) {
|
|
29
|
+
const block = `${MARK_START}\n${body}\n${MARK_END}`;
|
|
30
|
+
if (await exists(file)) {
|
|
31
|
+
const cur = await readFile(file, "utf8");
|
|
32
|
+
if (cur.includes(MARK_START) && cur.includes(MARK_END)) {
|
|
33
|
+
const next = cur.replace(new RegExp(`${esc(MARK_START)}[\\s\\S]*?${esc(MARK_END)}`), block);
|
|
34
|
+
await writeFile(file, next);
|
|
35
|
+
return "updated";
|
|
36
|
+
}
|
|
37
|
+
await writeFile(file, `${cur.replace(/\s*$/, "")}\n\n${block}\n`);
|
|
38
|
+
return "appended to";
|
|
39
|
+
}
|
|
40
|
+
await mkdir(dirname(file), { recursive: true });
|
|
41
|
+
await writeFile(file, `${block}\n`);
|
|
42
|
+
return "wrote";
|
|
43
|
+
}
|
|
44
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
45
|
+
|
|
46
|
+
async function copyInto(src, dst, { force }) {
|
|
47
|
+
if (!force && (await exists(dst))) return "skipped";
|
|
48
|
+
await mkdir(dirname(dst), { recursive: true });
|
|
49
|
+
await cp(src, dst, { recursive: true });
|
|
50
|
+
return "wrote";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} targetDir repo to install into (usually process.cwd())
|
|
55
|
+
* @param {object} opts { skill:"local"|"global"|"none", agents:string[], example:boolean, force:boolean }
|
|
56
|
+
* @param {string} assetsDir this package's assets/ dir (AGENTS.md, skill, examples)
|
|
57
|
+
* @returns {Promise<{steps:string[]}>}
|
|
58
|
+
*/
|
|
59
|
+
export async function init(targetDir, opts, assetsDir) {
|
|
60
|
+
const { skill = "local", agents = [], example = true, force = false } = opts;
|
|
61
|
+
const steps = [];
|
|
62
|
+
const withSkill = skill !== "none";
|
|
63
|
+
|
|
64
|
+
// 1. AGENTS.md — the universal language reference (every agent reads it).
|
|
65
|
+
const agentsBody = await readFile(join(assetsDir, "AGENTS.md"), "utf8");
|
|
66
|
+
const verb = await mergeMarkered(join(targetDir, "AGENTS.md"), agentsBody.trim());
|
|
67
|
+
steps.push(`${verb} AGENTS.md (the Loop language reference — any agent)`);
|
|
68
|
+
|
|
69
|
+
// 2. The Claude Code /loop skill.
|
|
70
|
+
if (withSkill) {
|
|
71
|
+
const dst = skill === "global"
|
|
72
|
+
? join(homedir(), ".claude", "skills", "loop")
|
|
73
|
+
: join(targetDir, ".claude", "skills", "loop");
|
|
74
|
+
const r = await copyInto(join(assetsDir, "skill"), dst, { force });
|
|
75
|
+
steps.push(`${r === "skipped" ? "skipped (exists)" : "wrote"} ${skill === "global" ? "~/.claude/skills/loop" : ".claude/skills/loop"} (the /loop skill)`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 3. Opt-in per-agent memory pointers.
|
|
79
|
+
const ptr = pointer({ skill: withSkill });
|
|
80
|
+
if (agents.includes("claude")) {
|
|
81
|
+
const v = await mergeMarkered(join(targetDir, "CLAUDE.md"), ptr);
|
|
82
|
+
steps.push(`${v} CLAUDE.md (Claude Code memory)`);
|
|
83
|
+
}
|
|
84
|
+
if (agents.includes("cursor")) {
|
|
85
|
+
const v = await mergeMarkered(join(targetDir, ".cursor", "rules", "loop.md"), ptr);
|
|
86
|
+
steps.push(`${v} .cursor/rules/loop.md (Cursor)`);
|
|
87
|
+
}
|
|
88
|
+
if (agents.includes("copilot")) {
|
|
89
|
+
const v = await mergeMarkered(join(targetDir, ".github", "copilot-instructions.md"), ptr);
|
|
90
|
+
steps.push(`${v} .github/copilot-instructions.md (GitHub Copilot)`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 4. A starter loop to run.
|
|
94
|
+
if (example) {
|
|
95
|
+
const r = await copyInto(join(assetsDir, "examples", "fix_test.loop"), join(targetDir, "examples", "fix_test.loop"), { force });
|
|
96
|
+
steps.push(`${r === "skipped" ? "skipped (exists)" : "wrote"} examples/fix_test.loop (a starter loop)`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { steps };
|
|
100
|
+
}
|