@lemoncode/lemony 0.1.1 → 0.2.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/catalog/VERSION +1 -1
- package/catalog/agents/architect.md +11 -4
- package/catalog/agents/implementer.md +46 -5
- package/catalog/agents/orchestrator.md +143 -345
- package/catalog/agents/reviewer.md +131 -8
- package/catalog/agents/spec-author.md +4 -1
- package/catalog/agents/spinoff.md +100 -0
- package/catalog/agents/triage.md +38 -0
- package/catalog/agents/ui-design.md +147 -0
- package/catalog/agents/ui-designer.md +3 -2
- package/catalog/commands/resume.md +1 -1
- package/catalog/commands/triage.md +2 -2
- package/catalog/hooks/init.sh +0 -0
- package/catalog/hooks/lib/lemony.sh +0 -0
- package/catalog/hooks/lib/playbook-scan.sh +60 -1
- package/catalog/hooks/lib/transcript-grep.sh +0 -0
- package/catalog/hooks/require-playbook.sh +0 -0
- package/catalog/hooks/session-close.sh +0 -0
- package/catalog/hooks/suggest-playbook.sh +0 -0
- package/catalog/schemas/tier2-events-history.md +17 -0
- package/catalog/schemas/tier2-events.md +10 -10
- package/catalog/skills/mutation-testing/SKILL.md +6 -2
- package/catalog/skills/prd-to-spec/SKILL.md +28 -2
- package/catalog/skills/resolve-discovery/SKILL.md +6 -2
- package/catalog/skills/spec-compliance-check/SKILL.md +8 -4
- package/catalog/skills/task-closeout/SKILL.md +3 -2
- package/catalog/skills/test-gap-report/SKILL.md +4 -0
- package/catalog/skills/verify/SKILL.md +3 -0
- package/catalog/templates/claude-code/agents.md.tpl +9 -4
- package/dist/cli.mjs +27 -10
- package/package.json +22 -27
|
@@ -149,14 +149,14 @@ Emitted by the Orchestrator when it transitions `spec-in-progress → spec-ready
|
|
|
149
149
|
Emitted by the Orchestrator at closeout (after `gh pr view` confirms `MERGED`,
|
|
150
150
|
before `git rm` of the task state).
|
|
151
151
|
|
|
152
|
-
| Field | Type | Required | Axis | Notes
|
|
153
|
-
| ------------------- | ------ | -------- | --------------- |
|
|
154
|
-
| `task_id` | string | yes | `identity` | Required for this type.
|
|
155
|
-
| `level` | string | yes | `internal-enum` | `L1` \| `L2` \| `L3` — the task-fit dial value used.
|
|
156
|
-
| `cycle_time_h` | number | yes | `metric` | Wall-clock hours from issue creation to merge. ≥ 0, finite.
|
|
157
|
-
| `review_rejections` | number | yes | `metric` | Count of `review_rejected` events for this `task_id` (≥ 0, int).
|
|
158
|
-
| `mode` | string | no | `internal-enum` | `all_at_once` \| `step_by_step` — the mode chosen at the L1 approval gate. **Absent on L2** (the question only exists where `tasks.md` does).
|
|
159
|
-
| `steps` | number | no | `metric` | Count of `step_completed` events for this task (≥ 1, int). Only meaningful when `mode` is `step_by_step`; < total
|
|
152
|
+
| Field | Type | Required | Axis | Notes |
|
|
153
|
+
| ------------------- | ------ | -------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
154
|
+
| `task_id` | string | yes | `identity` | Required for this type. |
|
|
155
|
+
| `level` | string | yes | `internal-enum` | `L1` \| `L2` \| `L3` — the task-fit dial value used. |
|
|
156
|
+
| `cycle_time_h` | number | yes | `metric` | Wall-clock hours from issue creation to merge. ≥ 0, finite. |
|
|
157
|
+
| `review_rejections` | number | yes | `metric` | Count of `review_rejected` events for this `task_id` (≥ 0, int). |
|
|
158
|
+
| `mode` | string | no | `internal-enum` | `all_at_once` \| `step_by_step` — the mode chosen at the L1 approval gate. **Absent on L2** (the question only exists where `tasks.md` does). |
|
|
159
|
+
| `steps` | number | no | `metric` | Count of `step_completed` events for this task (≥ 1, int). Only meaningful when `mode` is `step_by_step`; < total groups after a mid-task downgrade. |
|
|
160
160
|
|
|
161
161
|
### 5. `review_rejected` _(P5)_
|
|
162
162
|
|
|
@@ -168,7 +168,7 @@ dedicated label).
|
|
|
168
168
|
| `task_id` | string | yes | `identity` | Required for this type. |
|
|
169
169
|
| `reason` | string | yes | `free-text` | Short human-readable reason (one line; never the full review comment). 1-500 chars. |
|
|
170
170
|
| `iteration` | number | yes | `metric` | 1-based: the Nth rejection of this task (≥ 1, int). |
|
|
171
|
-
| `step` | number | no | `metric` | The step (1-based `tasks.md`
|
|
171
|
+
| `step` | number | no | `metric` | The step (1-based; one step = one `tasks.md` group) whose per-step review rejected. **Absent** on full-pass and all-at-once rejections. |
|
|
172
172
|
| `attributed_kind` | string | no | `internal-enum` | `agent` \| `skill` \| `playbook` — the kind of component the friction is attributed to. **Omitted when the emitter can't attribute.** |
|
|
173
173
|
| `attributed_name` | string | no | `internal-enum` | The component's name (free string, 1-200 chars), e.g. `implementer`. Independently optional in the schema; emitters pair it with `attributed_kind` and omit both when they can't attribute. Free-string by design — see [Attribution](#attribution). |
|
|
174
174
|
|
|
@@ -218,7 +218,7 @@ humans bail out (`ok_downgrade`).
|
|
|
218
218
|
| Field | Type | Required | Axis | Notes |
|
|
219
219
|
| ------------------- | ------ | -------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
220
220
|
| `task_id` | string | yes | `identity` | Required for this type. |
|
|
221
|
-
| `step` | number | yes | `metric` | 1-based
|
|
221
|
+
| `step` | number | yes | `metric` | 1-based step number the checkpoint belongs to (one step = one `tasks.md` group; ≥ 1, int). |
|
|
222
222
|
| `review_iterations` | number | yes | `metric` | Reviewer invocations that preceded this checkpoint (≥ 1, int — every step is reviewed before the human; resets after a "changes"). |
|
|
223
223
|
| `checkpoint_result` | string | yes | `internal-enum` | `ok` \| `changes` \| `ok_downgrade` (OK and switch the remaining tasks to all-at-once). |
|
|
224
224
|
| `attributed_kind` | string | no | `internal-enum` | `agent` \| `skill` \| `playbook` — the kind of component the friction is attributed to. **Omitted when the emitter can't attribute.** |
|
|
@@ -63,8 +63,12 @@ the verdict is cheaper than mis-routing a real gap to a dismissable offer.
|
|
|
63
63
|
### 3. Report
|
|
64
64
|
|
|
65
65
|
- **In-scope surviving mutants** → list them in your review verdict as an **advisory**
|
|
66
|
-
block: file:line, the mutation that survived, and the assertion that would have
|
|
67
|
-
it
|
|
66
|
+
block: file:line, the mutation that survived, and the assertion that would have
|
|
67
|
+
caught it — all of them when few (over-reporting here is cheaper than mis-routing,
|
|
68
|
+
per step 2). On a noisy run, carry the highest-value ones in the verdict and route
|
|
69
|
+
the full list to the full-pass issue comment; on a per-step review — which posts no
|
|
70
|
+
comment — anything worth keeping goes in the verdict bullets (the Orchestrator
|
|
71
|
+
records those in `progress.md`); what you leave out dies with your context. This is **not** a REJECT on its own (decision: advisory). The Implementer may
|
|
68
72
|
strengthen the tests; the Reviewer may still REJECT by _judgment_ if a survivor exposes
|
|
69
73
|
a genuinely dangerous untested path — but the mutation result alone never auto-blocks.
|
|
70
74
|
- **Pre-existing surviving mutants** → run **`note-side-finding`**: one `## Side-findings`
|
|
@@ -29,7 +29,7 @@ the gap by guessing — surface it (see "When the PRD is insufficient").
|
|
|
29
29
|
real from the start — there is no `draft-<slug>` holder:
|
|
30
30
|
- `requirements.md` — EARS requirements with acceptance criteria
|
|
31
31
|
- `design.md` — files, functions, approach, edge cases, testing
|
|
32
|
-
- `tasks.md` — atomic, ordered
|
|
32
|
+
- `tasks.md` — atomic, ordered checkbox tasks, grouped under risk-sized step headers
|
|
33
33
|
|
|
34
34
|
## Process
|
|
35
35
|
|
|
@@ -126,16 +126,40 @@ Atomic, ordered checkboxes. Each task is a **vertical slice** (a behavior, not a
|
|
|
126
126
|
layer) so the Implementer can run TDD red→green per task. Reference the requirements
|
|
127
127
|
each task satisfies.
|
|
128
128
|
|
|
129
|
+
Group the tasks under **risk-sized step headers**. In step-by-step mode the loop runs
|
|
130
|
+
one implement→review→human-checkpoint cycle **per group**, so the grouping decides how
|
|
131
|
+
often the human is interrupted; the human approves it at the spec gate with the rest
|
|
132
|
+
of the spec. Grouping criterion:
|
|
133
|
+
|
|
134
|
+
1. **Risk isolates — hard rule.** A task gets its own group iff it concentrates risk:
|
|
135
|
+
a new seam, a hard-to-reverse decision, a data-loss or security surface.
|
|
136
|
+
2. **Runnable surface closes — soft rule.** Among trivial stretches, prefer group
|
|
137
|
+
boundaries that leave something the human can run at the checkpoint. Never invent
|
|
138
|
+
a group to fabricate a demo.
|
|
139
|
+
3. **Soft cap: 4 tasks per group**, exceedable only with explicit justification in
|
|
140
|
+
the group's rationale (e.g. "5 identical mechanical renames").
|
|
141
|
+
4. **Every group header carries a one-line boundary rationale** so the grouping is
|
|
142
|
+
auditable at the spec gate.
|
|
143
|
+
|
|
129
144
|
```markdown
|
|
130
145
|
# Tasks — <topic>
|
|
131
146
|
|
|
147
|
+
## Group 1 — <name> _(<one-line boundary rationale>)_
|
|
148
|
+
|
|
132
149
|
- [ ] T1 — <smallest behavior that proves the path end-to-end> (R1)
|
|
133
150
|
- [ ] T2 — <next behavior> (R2)
|
|
151
|
+
|
|
152
|
+
## Group 2 — <name> _(<one-line boundary rationale>)_
|
|
153
|
+
|
|
134
154
|
- [ ] T3 — <error path> (R2, R3)
|
|
135
155
|
```
|
|
136
156
|
|
|
137
157
|
Rules: order so the first task is a tracer bullet; never "write all tests" then
|
|
138
|
-
"write all code"; keep each task small enough to verify on its own.
|
|
158
|
+
"write all code"; keep each task small enough to verify on its own. Grouping never
|
|
159
|
+
changes task granularity — checkboxes stay atomic and TDD runs per task; only review
|
|
160
|
+
and checkpoint frequency follow the groups (all-at-once mode ignores the headers).
|
|
161
|
+
Tasks added mid-implementation (from a discovery) default to **their own group** —
|
|
162
|
+
they are risk by definition.
|
|
139
163
|
|
|
140
164
|
### 5. Self-check before handing off
|
|
141
165
|
|
|
@@ -143,6 +167,8 @@ Rules: order so the first task is a tracer bullet; never "write all tests" then
|
|
|
143
167
|
- [ ] Every requirement is EARS-formatted and testable.
|
|
144
168
|
- [ ] At least one unwanted-behavior (`If … then …`) requirement exists.
|
|
145
169
|
- [ ] Every requirement is covered by at least one task.
|
|
170
|
+
- [ ] Every task sits in a group, and every group header carries a one-line
|
|
171
|
+
boundary rationale.
|
|
146
172
|
- [ ] No closed PRD decision is contradicted.
|
|
147
173
|
|
|
148
174
|
### 6. Emit `spec_created`
|
|
@@ -64,7 +64,9 @@ is a second discovery — append it, don't fold it in.
|
|
|
64
64
|
|
|
65
65
|
The agent that **created** an artifact is the one that updates it. Map the decision to
|
|
66
66
|
the artifact it changes, then invoke that owner (fresh context, Task tool) with the
|
|
67
|
-
decision
|
|
67
|
+
decision **stated in full** — at this moment it exists nowhere on disk; the entry's
|
|
68
|
+
`**Resolution**` block is only written at step 4 — plus the `discoveries.md` entry
|
|
69
|
+
**by path** for the surrounding context (it reads the entry itself):
|
|
68
70
|
|
|
69
71
|
| Artifact changed by the decision | Owner to invoke |
|
|
70
72
|
| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -104,7 +106,9 @@ do not edit the raise fields):
|
|
|
104
106
|
Same fail-loud contract: a non-zero exit means the flag/status did not flip back.
|
|
105
107
|
|
|
106
108
|
2. Re-invoke the paused sub-agent (fresh context) with: the issue, the task-state
|
|
107
|
-
paths,
|
|
109
|
+
paths, a one-line gist of the decision (the full `**Resolution**` block now
|
|
110
|
+
lives in the `discoveries.md` entry — reference it), and the updated artifact's
|
|
111
|
+
**path**. It reads the files itself and picks up from where it stopped.
|
|
108
112
|
|
|
109
113
|
## Closeout invariant
|
|
110
114
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spec-compliance-check
|
|
3
|
-
description: Walk an L1 spec point by point against the implementation and emit a per-requirement pass/fail verdict. Use during review of an SDD task to prove every EARS requirement (including unwanted-behavior paths) is satisfied and tested — a
|
|
3
|
+
description: Walk an L1 spec point by point against the implementation and emit a per-requirement pass/fail verdict. Use during review of an SDD task to prove every EARS requirement (including unwanted-behavior paths) is satisfied and tested — a durable audit trail. Complements `senior-review` (quality) with traceability (coverage of intent).
|
|
4
4
|
origin: vendor
|
|
5
5
|
vendor_version: '{{vendor_version}}'
|
|
6
6
|
phase: post-implementation
|
|
@@ -11,7 +11,7 @@ invoked-by: [reviewer]
|
|
|
11
11
|
|
|
12
12
|
`senior-review` judges whether the code is _good_; this skill proves it does what the
|
|
13
13
|
**spec said** — every requirement, point by point. It produces a traceability matrix:
|
|
14
|
-
a
|
|
14
|
+
a durable audit trail mapping each requirement to its implementation and its test.
|
|
15
15
|
Only meaningful for **L1 (SDD) tasks** that have a spec under
|
|
16
16
|
`.claude/state/tasks/<id>/spec/`; skip it for L2.
|
|
17
17
|
|
|
@@ -25,8 +25,8 @@ Read all three spec artifacts for the task:
|
|
|
25
25
|
state-driven / optional-feature / **unwanted-behavior**), each numbered with
|
|
26
26
|
acceptance criteria.
|
|
27
27
|
- `design.md` — the intended files, interfaces, approach, edge cases.
|
|
28
|
-
- `tasks.md` — the atomic checklist
|
|
29
|
-
satisfies.
|
|
28
|
+
- `tasks.md` — the atomic checklist grouped under risk-sized step headers, each
|
|
29
|
+
item referencing the requirements it satisfies.
|
|
30
30
|
|
|
31
31
|
### 2. Trace each requirement to the implementation
|
|
32
32
|
|
|
@@ -55,6 +55,10 @@ and asserts the rejection/guard.
|
|
|
55
55
|
|
|
56
56
|
### 4. Emit the matrix
|
|
57
57
|
|
|
58
|
+
Destination: the full-pass **issue comment**. In your return summary — and always on
|
|
59
|
+
a per-step review, which posts no comment — carry only the counts + the failing
|
|
60
|
+
rows, per the Reviewer's verdict-size rule.
|
|
61
|
+
|
|
58
62
|
```
|
|
59
63
|
## Spec Compliance — task #<id>
|
|
60
64
|
|
|
@@ -87,8 +87,9 @@ The three differ in **who decides** — and the asymmetry is deliberate:
|
|
|
87
87
|
|
|
88
88
|
- **`update-architecture` — automatic dispatch, no pre-offer.** Only when
|
|
89
89
|
`docs/architecture.md` exists (the skill installs solely then). Dispatch the
|
|
90
|
-
**Architect** (`update-architecture`, fresh context) with the task's **
|
|
91
|
-
(`gh pr diff <pr>`) plus the task's `spec/design.md`
|
|
90
|
+
**Architect** (`update-architecture`, fresh context) with the task's **PR number**
|
|
91
|
+
(it runs `gh pr diff <pr>` itself) plus the **path** of the task's `spec/design.md`
|
|
92
|
+
(still live here — archival is
|
|
92
93
|
step 4 below). The Architect reads the change, makes
|
|
93
94
|
the smallest true edit if the system's **shape** moved (referencing any ADR just
|
|
94
95
|
written — `see ADR-NNNN`), or reports **no-op** when nothing architectural changed.
|
|
@@ -52,6 +52,10 @@ For each file in the "always" set:
|
|
|
52
52
|
|
|
53
53
|
### 3. Report
|
|
54
54
|
|
|
55
|
+
Destination: the full-pass **issue comment**. In your return summary — and always on
|
|
56
|
+
a per-step review, which posts no comment — carry only the counts + the gaps, per
|
|
57
|
+
the Reviewer's verdict-size rule (a REJECT lifts the cap entirely).
|
|
58
|
+
|
|
55
59
|
```
|
|
56
60
|
## Test Gap Report — <task name>
|
|
57
61
|
|
|
@@ -84,6 +84,9 @@ and the error output (unexpected warnings/errors even when the happy path worked
|
|
|
84
84
|
**Verdict**: works / does not work — <one-line reason>
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
This block is your working record; a capped return summary condenses it to the
|
|
88
|
+
verdict line (plus any ❌ detail — failures are signal, never trimmed).
|
|
89
|
+
|
|
87
90
|
On a failure, read the **full** error, find the origin (not where it surfaced), fix
|
|
88
91
|
the root cause, and re-run from the failed gate. If a failure reveals the spec itself
|
|
89
92
|
is wrong or silent, that's a **discovery** — run `raise-discovery`, don't paper over
|
|
@@ -57,7 +57,8 @@ A task deserves the harness if it is **specifiable**, **verifiable**, or
|
|
|
57
57
|
`harness:sdd` + `harness:status:spec-in-progress`) and the branch
|
|
58
58
|
`harness/<id>-<slug>`. `<id>` is the GitHub issue number in this build; spec and code
|
|
59
59
|
both live on that branch — nothing touches the default branch until the merge gate.
|
|
60
|
-
3. **UI design (if it touches UI)** — **UI design gate** (
|
|
60
|
+
3. **UI design (if it touches UI)** — **UI design gate** (canonical: the orchestrator's
|
|
61
|
+
L1 step 3, evaluated core-only — read `.claude/agents/ui-design.md` only when it fires):
|
|
61
62
|
if the repo has a frontend AND the task touches UI, put `harness:needs-design`, offer the
|
|
62
63
|
design-stop, and on "continue" **run `grill-ui` yourself** (the interactive design
|
|
63
64
|
interview) to author `ui-handoff.md` under `tasks/<id>/spec/`, then dispatch the **UI
|
|
@@ -67,7 +68,7 @@ A task deserves the harness if it is **specifiable**, **verifiable**, or
|
|
|
67
68
|
under `tasks/<id>/spec/`) then `spec-to-issue` (fills the issue body — it creates nothing
|
|
68
69
|
and moves no labels).
|
|
69
70
|
5. **Spec-ready + handoff** — remove `harness:needs-design` once `ui-handoff.md` is
|
|
70
|
-
complete (a spec-ready task never carries
|
|
71
|
+
complete (a spec-ready task never carries the label), flip to `harness:status:spec-ready`,
|
|
71
72
|
commit and push the task state to the branch. DEFINE can stop here: the spec-ready
|
|
72
73
|
queue (`gh issue list -l harness:status:spec-ready`) is the handoff. Ask: implement
|
|
73
74
|
now or hand off?
|
|
@@ -83,8 +84,10 @@ A task deserves the harness if it is **specifiable**, **verifiable**, or
|
|
|
83
84
|
— either lens rejecting routes back to the Implementer.
|
|
84
85
|
9. **Merge gate** — never auto-merge. Surface the approved PR; the human merges (or
|
|
85
86
|
authorizes you to). The task stays at `in-review` until merged.
|
|
86
|
-
10. **Closeout** — `task-closeout`: confirm the merge via `gh`,
|
|
87
|
-
|
|
87
|
+
10. **Closeout** — `task-closeout`: confirm the merge via `gh`, run the three Architect
|
|
88
|
+
activations (`write-adr` — HITL offer per resolved discovery; `update-architecture` —
|
|
89
|
+
automatic when `docs/architecture.md` exists; `playbook-iterate` — HITL offer once
|
|
90
|
+
per task), archive the spec + discoveries to `_archive/<id>/` (drop
|
|
88
91
|
`progress.md`), and land `history.md` + the archival via a dedicated
|
|
89
92
|
`harness/closeout-<id>` PR (`gh pr merge --auto`) — never a direct push to the base.
|
|
90
93
|
If protection needs approval the PR waits: park at `closeout-pending`, `/resume`
|
|
@@ -92,6 +95,8 @@ A task deserves the harness if it is **specifiable**, **verifiable**, or
|
|
|
92
95
|
|
|
93
96
|
## L2 round-trip (TRIAGE)
|
|
94
97
|
|
|
98
|
+
Authority for the steps: `.claude/agents/triage.md` (the Orchestrator's lazy companion).
|
|
99
|
+
|
|
95
100
|
1. **Triage** — `triage-issue`: investigate, find root cause, draft a TDD-based fix
|
|
96
101
|
plan, and create the issue with `harness:managed` (no `harness:sdd` — its absence
|
|
97
102
|
marks the lightweight path).
|
package/dist/cli.mjs
CHANGED
|
@@ -45,7 +45,8 @@ const formatIssue = (schema, issue) => {
|
|
|
45
45
|
const valid = validKeysAt(schema, issue.path);
|
|
46
46
|
return issue.keys.map((key) => {
|
|
47
47
|
const guess = valid ? nearestKey(key, valid) : null;
|
|
48
|
-
|
|
48
|
+
const hint = guess ? ` Did you mean "${guess}"?` : "";
|
|
49
|
+
return `Unknown key "${key}" in ${where}.${hint}`;
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
if (issue.code === "invalid_format" && issue.format === "regex") {
|
|
@@ -206,7 +207,8 @@ const setConfigValues = (rawYaml, updates) => {
|
|
|
206
207
|
};
|
|
207
208
|
const writeConfigValues = async (repoRoot, updates) => {
|
|
208
209
|
const configPath = join(repoRoot, HARNESS_CONFIG_FILENAME);
|
|
209
|
-
|
|
210
|
+
const raw = await readFile(configPath, "utf8");
|
|
211
|
+
await writeFile(configPath, setConfigValues(raw, updates));
|
|
210
212
|
};
|
|
211
213
|
//#endregion
|
|
212
214
|
//#region src/config/pointer.schema.ts
|
|
@@ -1079,7 +1081,8 @@ const runValidate = async (inputs) => {
|
|
|
1079
1081
|
filesScanned: 0,
|
|
1080
1082
|
violations: []
|
|
1081
1083
|
};
|
|
1082
|
-
const
|
|
1084
|
+
const raw = await readFile(tokenPath, "utf8");
|
|
1085
|
+
const fileCheck = validateTokenFile(raw);
|
|
1083
1086
|
if (!fileCheck.ok) return {
|
|
1084
1087
|
ok: false,
|
|
1085
1088
|
tokensFound: true,
|
|
@@ -1410,7 +1413,8 @@ const linearizeChannel = (channel) => {
|
|
|
1410
1413
|
const relativeLuminance = ({ r, g, b }) => .2126 * linearizeChannel(r) + .7152 * linearizeChannel(g) + .0722 * linearizeChannel(b);
|
|
1411
1414
|
const contrastRatio = (foreground, background) => {
|
|
1412
1415
|
const bg = background.a < 1 ? flatten(background, WHITE) : background;
|
|
1413
|
-
const
|
|
1416
|
+
const fg = foreground.a < 1 ? flatten(foreground, bg) : foreground;
|
|
1417
|
+
const l1 = relativeLuminance(fg);
|
|
1414
1418
|
const l2 = relativeLuminance(bg);
|
|
1415
1419
|
const light = Math.max(l1, l2);
|
|
1416
1420
|
const dark = Math.min(l1, l2);
|
|
@@ -1457,7 +1461,9 @@ const runContrast = async (inputs) => {
|
|
|
1457
1461
|
};
|
|
1458
1462
|
const tokens = collectTokens(parsed);
|
|
1459
1463
|
const problems = [];
|
|
1460
|
-
const
|
|
1464
|
+
const specs = discoverPairs(tokens, problems);
|
|
1465
|
+
const modes = declaredModes(tokens);
|
|
1466
|
+
const pairs = measurePairs(specs, modes, tokens);
|
|
1461
1467
|
return {
|
|
1462
1468
|
ok: problems.length === 0 && pairs.every((pair) => pair.passes),
|
|
1463
1469
|
tokensFound: true,
|
|
@@ -3303,7 +3309,8 @@ const runStatus = async (deps) => {
|
|
|
3303
3309
|
const config = await readHarnessConfig(repoRoot);
|
|
3304
3310
|
const pointer = await readPointer(repoRoot, deps.readGitUserEmail);
|
|
3305
3311
|
const { branch, behind } = await deps.gitBehind(repoRoot);
|
|
3306
|
-
const
|
|
3312
|
+
const provider = createTaskTrackerProvider(config.task_storage.type, deps.runCommand);
|
|
3313
|
+
const openDiscoveries = await countOpenDiscoveries(provider, config.task_storage.repo);
|
|
3307
3314
|
return {
|
|
3308
3315
|
vendorVersion: config.vendor_version,
|
|
3309
3316
|
taskStorageRepo: config.task_storage.repo,
|
|
@@ -3659,7 +3666,10 @@ const hasConflictMarkers = (content) => CONFLICT_FENCE.test(content);
|
|
|
3659
3666
|
//#endregion
|
|
3660
3667
|
//#region src/merge/three-way-merge.ts
|
|
3661
3668
|
const threeWayMerge = (base, client, vendor) => {
|
|
3662
|
-
const
|
|
3669
|
+
const baseLines = base.split("\n");
|
|
3670
|
+
const clientLines = client.split("\n");
|
|
3671
|
+
const vendorLines = vendor.split("\n");
|
|
3672
|
+
const regions = diff3Regions(baseLines, clientLines, vendorLines);
|
|
3663
3673
|
const out = [];
|
|
3664
3674
|
let conflicted = false;
|
|
3665
3675
|
for (const region of regions) {
|
|
@@ -3920,6 +3930,12 @@ const CORE_AGENTS = [
|
|
|
3920
3930
|
"architect",
|
|
3921
3931
|
"ui-designer"
|
|
3922
3932
|
];
|
|
3933
|
+
const COMPANION_DOCS = [
|
|
3934
|
+
"fit-assessment",
|
|
3935
|
+
"triage",
|
|
3936
|
+
"spinoff",
|
|
3937
|
+
"ui-design"
|
|
3938
|
+
];
|
|
3923
3939
|
const renderFile = async (templatePath, relPath, vars) => {
|
|
3924
3940
|
return {
|
|
3925
3941
|
relPath,
|
|
@@ -3945,7 +3961,7 @@ const materializeVendorFiles = async (ctx) => {
|
|
|
3945
3961
|
SKILLS: renderRoleSkills(skills, role),
|
|
3946
3962
|
vendor_version: vendorVersion
|
|
3947
3963
|
}), vendorVersion)));
|
|
3948
|
-
const
|
|
3964
|
+
const companionFiles = await Promise.all(COMPANION_DOCS.map((doc) => copyFileEntry(join(vendorRoot, "agents", `${doc}.md`), join(AGENTS_DIR, `${doc}.md`))));
|
|
3949
3965
|
const skillFiles = (await Promise.all(skills.map(async (skill) => {
|
|
3950
3966
|
const skillDir = join(vendorRoot, "skills", skill.name);
|
|
3951
3967
|
const rels = await listFiles(skillDir);
|
|
@@ -3962,7 +3978,7 @@ const materializeVendorFiles = async (ctx) => {
|
|
|
3962
3978
|
return [
|
|
3963
3979
|
entryProtocol,
|
|
3964
3980
|
...agentFiles,
|
|
3965
|
-
|
|
3981
|
+
...companionFiles,
|
|
3966
3982
|
...skillFiles,
|
|
3967
3983
|
playbooksReadme,
|
|
3968
3984
|
...hookFiles,
|
|
@@ -4824,12 +4840,13 @@ const update = async (args) => {
|
|
|
4824
4840
|
const repoRoot = cwd();
|
|
4825
4841
|
const onConflict = parseOnConflict(args);
|
|
4826
4842
|
const dryRun = args.includes("--dry-run");
|
|
4843
|
+
const allowDowngrade = args.includes("--allow-downgrade");
|
|
4827
4844
|
const result = await runUpdate({
|
|
4828
4845
|
repoRoot,
|
|
4829
4846
|
vendorRoot: VENDOR_ROOT,
|
|
4830
4847
|
onConflict,
|
|
4831
4848
|
dryRun,
|
|
4832
|
-
allowDowngrade
|
|
4849
|
+
allowDowngrade,
|
|
4833
4850
|
runCommand: makeRunCommand()
|
|
4834
4851
|
});
|
|
4835
4852
|
const verb = (applied, preview) => dryRun ? preview : applied;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lemoncode/lemony",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Lemony — a Harness for AI Coding. Vendor package: installer, agent role catalog, generic skill catalog, hooks, and templates for a Spec-Driven Development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"engines": {
|
|
30
30
|
"node": ">=24",
|
|
31
|
-
"
|
|
31
|
+
"pnpm": ">=11.5.1"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"registry": "https://registry.npmjs.org",
|
|
@@ -43,6 +43,24 @@
|
|
|
43
43
|
"agents",
|
|
44
44
|
"scaffolding"
|
|
45
45
|
],
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@changesets/cli": "2.31.1",
|
|
48
|
+
"@stryker-mutator/core": "9.6.1",
|
|
49
|
+
"@stryker-mutator/vitest-runner": "9.6.1",
|
|
50
|
+
"@types/node": "24.13.3",
|
|
51
|
+
"husky": "9.1.7",
|
|
52
|
+
"lint-staged": "17.2.0",
|
|
53
|
+
"oxlint": "1.75.0",
|
|
54
|
+
"prettier": "3.9.6",
|
|
55
|
+
"tsdown": "0.22.14",
|
|
56
|
+
"tsx": "4.23.1",
|
|
57
|
+
"typescript": "7.0.2",
|
|
58
|
+
"vitest": "4.1.10"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"yaml": "2.9.0",
|
|
62
|
+
"zod": "4.4.3"
|
|
63
|
+
},
|
|
46
64
|
"scripts": {
|
|
47
65
|
"build": "tsdown",
|
|
48
66
|
"check-types": "tsc --noEmit",
|
|
@@ -60,30 +78,7 @@
|
|
|
60
78
|
"notice:check": "tsx scripts/generate-notice.ts --check",
|
|
61
79
|
"telemetry:aggregate": "tsx scripts/aggregate-telemetry.ts",
|
|
62
80
|
"changeset": "changeset",
|
|
63
|
-
"changeset:version": "changeset version && tsx scripts/sync-catalog-version.ts",
|
|
81
|
+
"changeset:version": "changeset version && pnpm install --lockfile-only && tsx scripts/sync-catalog-version.ts",
|
|
64
82
|
"changeset:publish": "node --run build && changeset publish"
|
|
65
|
-
},
|
|
66
|
-
"devDependencies": {
|
|
67
|
-
"@changesets/cli": "2.31.0",
|
|
68
|
-
"@stryker-mutator/core": "9.6.1",
|
|
69
|
-
"@stryker-mutator/vitest-runner": "9.6.1",
|
|
70
|
-
"@types/node": "24.13.2",
|
|
71
|
-
"oxlint": "1.71.0",
|
|
72
|
-
"prettier": "3.9.1",
|
|
73
|
-
"tsdown": "0.22.3",
|
|
74
|
-
"tsx": "4.22.4",
|
|
75
|
-
"typescript": "6.0.3",
|
|
76
|
-
"vitest": "4.1.9"
|
|
77
|
-
},
|
|
78
|
-
"dependencies": {
|
|
79
|
-
"yaml": "2.9.0",
|
|
80
|
-
"zod": "4.4.3"
|
|
81
|
-
},
|
|
82
|
-
"overrides": {
|
|
83
|
-
"typed-rest-client": {
|
|
84
|
-
"qs": "6.15.2"
|
|
85
|
-
},
|
|
86
|
-
"js-yaml": "4.2.0",
|
|
87
|
-
"vite": "8.0.16"
|
|
88
83
|
}
|
|
89
|
-
}
|
|
84
|
+
}
|