kodelyth-ecc 1.8.0 → 1.8.2
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/CLAUDE.md +1 -1
- package/VERSION +1 -1
- package/bin/kodelyth-ecc.js +54 -5
- package/install.ps1 +30 -13
- package/install.sh +11 -4
- package/package.json +1 -1
- package/rules/common/agent-intent-routing.md +96 -195
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -47
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -41
- package/.github/ISSUE_TEMPLATE/good_first_issue.md +0 -36
- package/.github/ISSUE_TEMPLATE/new_agent.md +0 -52
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -53
- package/.github/workflows/ci.yml +0 -45
- package/.github/workflows/publish.yml +0 -60
- package/brand/concepts.svg +0 -109
- package/brand/convert.js +0 -161
- package/brand/favicon.svg +0 -7
- package/brand/fb-cover.svg +0 -34
- package/brand/fb-profile.svg +0 -21
- package/brand/kodelyth-dark.svg +0 -24
- package/brand/kodelyth-light.svg +0 -24
- package/brand/kodelyth-mark.svg +0 -20
- package/brand/package.json +0 -12
- package/docs/dashboard.md +0 -211
- package/docs/evolve.md +0 -303
- package/docs/mcp-clients.md +0 -167
- package/docs/mcp.md +0 -178
- package/docs/replay.md +0 -244
- package/docs/supply-chain.md +0 -207
- package/docs/swarm.md +0 -243
- package/skills/skill-comply/tests/test_grader.py +0 -137
- package/skills/skill-comply/tests/test_parser.py +0 -90
- package/social/card-agents.svg +0 -104
- package/social/card-install.svg +0 -58
- package/social/card-main.svg +0 -54
- package/social/facebook-v150.svg +0 -110
- package/social/fb-ad-main.svg +0 -128
- package/social/fb-post-features.svg +0 -118
- package/social/fb-post-launch.svg +0 -144
- package/social/fb-post-platforms.svg +0 -135
- package/social/github-social-preview.svg +0 -151
- package/social/hype-compound-learning.svg +0 -129
- package/social/hype-devil-mode.svg +0 -240
- package/social/hype-mcp-server.svg +0 -148
- package/social/hype-parallel-agents.svg +0 -162
- package/social/hype-stats-hero.svg +0 -138
- package/social/og-image.svg +0 -151
- package/social/readme-agents.svg +0 -138
- package/social/readme-hero.svg +0 -102
- package/social/section-agents.svg +0 -69
- package/social/section-author.svg +0 -66
- package/social/section-dashboard.svg +0 -71
- package/social/section-devil.svg +0 -66
- package/social/section-hooks.svg +0 -63
- package/social/section-install.svg +0 -54
- package/social/section-learning.svg +0 -64
- package/social/section-mcp.svg +0 -58
- package/social/section-memory.svg +0 -69
- package/social/section-parallel.svg +0 -84
- package/social/section-routing.svg +0 -63
- package/social/twitter-threads.md +0 -621
- package/social/x-card-agents-grid.svg +0 -120
- package/social/x-card-free.svg +0 -71
- package/social/x-card-hook.svg +0 -94
- package/tests/dashboard/data.test.js +0 -391
- package/tests/dashboard/server.test.js +0 -257
- package/tests/evolve/analyze.test.js +0 -169
- package/tests/evolve/proposals.test.js +0 -173
- package/tests/evolve/stats.test.js +0 -167
- package/tests/hooks/branch-name-check.test.js +0 -184
- package/tests/hooks/smart-suggest.test.js +0 -149
- package/tests/hooks/test-reminder.test.js +0 -181
- package/tests/mcp/catalog.test.js +0 -98
- package/tests/mcp/client.test.js +0 -113
- package/tests/mcp/resources-prompts.test.js +0 -70
- package/tests/mcp/tools.test.js +0 -159
- package/tests/memory/auto-recall.test.js +0 -132
- package/tests/memory/instincts.test.js +0 -258
- package/tests/memory/store.test.js +0 -203
- package/tests/replay/bundle.test.js +0 -181
- package/tests/replay/replay.test.js +0 -131
- package/tests/router/classify.test.js +0 -164
- package/tests/run-all.js +0 -55
- package/tests/safety/patterns.test.js +0 -94
- package/tests/safety/prompt-injection-guard.test.js +0 -111
- package/tests/safety/token-budget.test.js +0 -119
- package/tests/supply-chain/manifest.test.js +0 -147
- package/tests/supply-chain/sbom.test.js +0 -170
- package/tests/supply-chain/verify.test.js +0 -146
- package/tests/swarm/build-plan.test.js +0 -188
package/docs/swarm.md
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
# Kodelyth ECC — Swarm Orchestrator
|
|
2
|
-
|
|
3
|
-
Run N ECC specialist agents in parallel inside isolated git worktrees, coordinated by a tmux session.
|
|
4
|
-
|
|
5
|
-
> **Phase 2.7 of the [Devil Roadmap](../README.md).** Promotes the existing tmux-worktree orchestrator infrastructure to a first-class CLI surface. The generalized form of `/devil-mode` — pick any task, pick any agents, get N panes ready to attach.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Why swarm
|
|
10
|
-
|
|
11
|
-
A single specialist agent has tunnel vision. A pre-flight production change usually needs:
|
|
12
|
-
|
|
13
|
-
- Code quality review (`code-reviewer`)
|
|
14
|
-
- Security audit (`security-reviewer`)
|
|
15
|
-
- API contract check (`api-guardian`)
|
|
16
|
-
- Test coverage check (`tdd-guide`)
|
|
17
|
-
- UX/a11y pass (`ux-reviewer`)
|
|
18
|
-
- Performance check (`performance-optimizer`)
|
|
19
|
-
- Doc update (`doc-updater`)
|
|
20
|
-
- Release readiness (`release-captain`)
|
|
21
|
-
|
|
22
|
-
Sequential = 2 hours. Parallel = 15 minutes. Each runs in its own git worktree so their changes don't collide; their handoffs merge into one folder for human review.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## CLI quick reference
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
# Auto-pick from task signals
|
|
30
|
-
npx kodelyth-ecc swarm --task "audit oauth flow for security regressions" --agents 4
|
|
31
|
-
|
|
32
|
-
# Explicit agent list
|
|
33
|
-
npx kodelyth-ecc swarm \
|
|
34
|
-
--task "ship v2.0" \
|
|
35
|
-
--agents release-captain,security-reviewer,e2e-runner,code-reviewer \
|
|
36
|
-
--execute
|
|
37
|
-
|
|
38
|
-
# Power-user plan.json
|
|
39
|
-
npx kodelyth-ecc swarm --plan plan.json --execute
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
| Flag | Description |
|
|
43
|
-
|---|---|
|
|
44
|
-
| `--task "..."` | Required (unless using `--plan`). The shared task all workers receive. |
|
|
45
|
-
| `--agents N` | Smart-pick N specialists from task signals + baseline + rotation. |
|
|
46
|
-
| `--agents name1,name2,...` | Explicit agent list. |
|
|
47
|
-
| `--harness <h>` | `claude` / `codex` / `opencode` / `windsurf` / `echo`. Default `claude`. |
|
|
48
|
-
| `--launcher-cmd "<tmpl>"` | Custom launcher template (overrides `--harness`). |
|
|
49
|
-
| `--seed <path>` | Overlay a path from the main repo into each worktree. Repeatable. |
|
|
50
|
-
| `--session <name>` | Override the auto-generated tmux session name. |
|
|
51
|
-
| `--worktree-root <dir>` | Where to create worktrees. Default: parent of repo. |
|
|
52
|
-
| `--coordination-root <dir>` | Where to write coordination files. Default: `<repo>/.orchestration/`. |
|
|
53
|
-
| `--base-ref <ref>` | Branch base for all worktrees. Default: `HEAD`. |
|
|
54
|
-
| `--replace` | Tear down any existing session/worktrees/branches with the same names. |
|
|
55
|
-
| `--execute` | Actually create worktrees + tmux + launch. |
|
|
56
|
-
| `--write-only` | Just write coordination files. |
|
|
57
|
-
| `--json` | Print the full plan as JSON. |
|
|
58
|
-
| `--plan plan.json` | Use a hand-written plan (skips auto-build). |
|
|
59
|
-
|
|
60
|
-
Default mode is **dry-run** — prints a summary and does NOT spawn anything. Always inspect first.
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## Smart agent picking
|
|
65
|
-
|
|
66
|
-
When you pass `--agents N` (a number), the swarm picks specialists in this priority order:
|
|
67
|
-
|
|
68
|
-
### 1. Signal-driven (highest priority)
|
|
69
|
-
|
|
70
|
-
Task text is scanned for 14 signal classes. Each match adds a specialist. Examples:
|
|
71
|
-
|
|
72
|
-
| Task contains | Picks |
|
|
73
|
-
|---|---|
|
|
74
|
-
| `security`, `auth`, `vuln`, `cve`, `injection`, `owasp` | `security-reviewer` |
|
|
75
|
-
| `perf`, `slow`, `p99`, `latency`, `bottleneck` | `performance-optimizer` |
|
|
76
|
-
| `load test`, `stress test`, `capacity`, `k6` | `load-tester` |
|
|
77
|
-
| `architect`, `design`, `ADR`, `RFC`, `trade-off` | `architect` |
|
|
78
|
-
| `api`, `endpoint`, `contract`, `breaking change` | `api-guardian` |
|
|
79
|
-
| `test`, `tdd`, `coverage`, `spec` | `tdd-guide` |
|
|
80
|
-
| `refactor`, `tech debt`, `code smell` | `refactor-cleaner` |
|
|
81
|
-
| `ux`, `accessibility`, `wcag`, `a11y` | `ux-reviewer` |
|
|
82
|
-
| `doc`, `readme`, `guide`, `tutorial` | `doc-updater` |
|
|
83
|
-
| `release`, `ship`, `tag`, `semver` | `release-captain` |
|
|
84
|
-
| `incident`, `outage`, `down`, `P0`, `P1` | `incident-commander` |
|
|
85
|
-
| `debug`, `why is`, `stack trace`, `silent fail` | `debug-detective` |
|
|
86
|
-
| `database`, `sql`, `postgres`, `index`, `n+1` | `database-reviewer` |
|
|
87
|
-
| `devil-mode`, `adversarial`, `red team` | `prompt-injection-hunter` |
|
|
88
|
-
|
|
89
|
-
### 2. Baseline anchors
|
|
90
|
-
|
|
91
|
-
`code-reviewer` and `pair-programmer` are added if not already picked — every task benefits from generalist eyes.
|
|
92
|
-
|
|
93
|
-
### 3. Rotation fill
|
|
94
|
-
|
|
95
|
-
If we still need more, fills from a tuned default rotation:
|
|
96
|
-
|
|
97
|
-
| Count | Rotation |
|
|
98
|
-
|---|---|
|
|
99
|
-
| 4 | code-reviewer, security-reviewer, pair-programmer, tdd-guide |
|
|
100
|
-
| 6 | + performance-optimizer, api-guardian |
|
|
101
|
-
| 8 | + ux-reviewer, doc-updater |
|
|
102
|
-
|
|
103
|
-
You can always override the picker with `--agents code-reviewer,security-reviewer,architect`.
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## Coordination protocol
|
|
108
|
-
|
|
109
|
-
Each worker gets three files in `<coord-root>/<session>/<worker-slug>/`:
|
|
110
|
-
|
|
111
|
-
### `task.md` (auto-generated)
|
|
112
|
-
|
|
113
|
-
Agent-shaped task with required handoff sections:
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
# <agent> — swarm task
|
|
117
|
-
|
|
118
|
-
You are running as the ECC `<agent>` specialist in a parallel swarm. Other agents
|
|
119
|
-
are running the same shared task in sibling worktrees. Stay strictly inside your
|
|
120
|
-
specialty — do not duplicate what other agents will cover. Produce a focused
|
|
121
|
-
handoff that names exactly which findings are yours and yours alone.
|
|
122
|
-
|
|
123
|
-
## Shared Task
|
|
124
|
-
<your task>
|
|
125
|
-
|
|
126
|
-
## Required handoff sections
|
|
127
|
-
1. Summary
|
|
128
|
-
2. Files Changed
|
|
129
|
-
3. Validation
|
|
130
|
-
4. Remaining Risks
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### `handoff.md` (worker fills in)
|
|
134
|
-
|
|
135
|
-
Where the worker's output lands. Watched by the launcher.
|
|
136
|
-
|
|
137
|
-
### `status.md` (launcher updates)
|
|
138
|
-
|
|
139
|
-
`running` / `completed` / `failed` plus timestamp + branch + worktree path.
|
|
140
|
-
|
|
141
|
-
Monitor a swarm in flight:
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
# All worker statuses
|
|
145
|
-
cat .orchestration/<session>/*/status.md
|
|
146
|
-
|
|
147
|
-
# Specific handoff
|
|
148
|
-
cat .orchestration/<session>/security-reviewer/handoff.md
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Harness adapters
|
|
154
|
-
|
|
155
|
-
| `--harness` | Launcher template |
|
|
156
|
-
|---|---|
|
|
157
|
-
| `claude` | `claude --print --dangerously-skip-permissions "$(cat {task_file_sh})" 2>&1 \| tee -a {handoff_file_sh}; ...` |
|
|
158
|
-
| `codex` | `bash {repo_root}/scripts/orchestrate-codex-worker.sh {task_file_sh} {handoff_file_sh} {status_file_sh}` |
|
|
159
|
-
| `opencode` | `opencode run --task-file {task_file_sh} --output {handoff_file_sh} --status {status_file_sh}` |
|
|
160
|
-
| `windsurf` | `windsurf-cli run --task-file {task_file_sh} --output {handoff_file_sh}` |
|
|
161
|
-
| `echo` | `printf 'demo {worker_slug}\n' >> {handoff_file_sh}; printf 'state: completed\n' >> {status_file_sh}` |
|
|
162
|
-
|
|
163
|
-
Custom harness via `--launcher-cmd`. Available placeholders:
|
|
164
|
-
|
|
165
|
-
```
|
|
166
|
-
{worker_name} {worker_slug} {session_name} {repo_root}
|
|
167
|
-
{worktree_path} {branch_name} {task_file} {handoff_file} {status_file}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Suffix with `_sh` for shell-quoted variants: `{task_file_sh}`, `{handoff_file_sh}`, etc.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## Worktree lifecycle
|
|
175
|
-
|
|
176
|
-
1. **Pre-flight** — verify `git rev-parse --is-inside-work-tree` and `tmux -V`.
|
|
177
|
-
2. **Cleanup** — if `--replace`, tear down any existing session, worktrees, branches.
|
|
178
|
-
3. **Materialize** — write coordination files for every worker.
|
|
179
|
-
4. **Branch + worktree** — `git worktree add -b orchestrator-<session>-<slug> <path> <base-ref>`.
|
|
180
|
-
5. **Seed overlay** — copy any `--seed` paths into each worktree.
|
|
181
|
-
6. **Tmux session** — `tmux new-session -d -s <session> -n orchestrator -c <repo>`.
|
|
182
|
-
7. **Per-worker pane** — `tmux split-window` + `select-pane` + `send-keys` to launch.
|
|
183
|
-
|
|
184
|
-
On failure, automatic rollback removes worktrees, branches, and the tmux session in reverse order.
|
|
185
|
-
|
|
186
|
-
Manually clean up later:
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
tmux kill-session -t <session>
|
|
190
|
-
git worktree list # verify
|
|
191
|
-
git worktree remove <path>
|
|
192
|
-
git branch -D orchestrator-<session>-<slug>
|
|
193
|
-
rm -rf .orchestration/<session>
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
---
|
|
197
|
-
|
|
198
|
-
## Pairing with the rest of ECC
|
|
199
|
-
|
|
200
|
-
| Pairs with | How |
|
|
201
|
-
|---|---|
|
|
202
|
-
| **`/devil-mode`** | Devil-mode is a hardcoded swarm of adversarial agents. Swarm generalizes it. |
|
|
203
|
-
| **Phase 2.4 cost router** | Each worker classifies its own task tier; security/incident workers force hard. |
|
|
204
|
-
| **Phase 2.10 token-budget hook** | Each worker has its own token budget — one rogue worker can't blow the cap. |
|
|
205
|
-
| **Phase 2.5 MCP client mode** | Workers can call registered external MCP servers (github, postgres, brave, redis). |
|
|
206
|
-
| **kodelyth-memory** | Save the merged handoff bundle: `kodelyth-ecc remember "..." --approach "..."`. |
|
|
207
|
-
| **`release-captain`** | After a swarm, run `release-captain` to merge the best work and ship. |
|
|
208
|
-
|
|
209
|
-
---
|
|
210
|
-
|
|
211
|
-
## Hard rules
|
|
212
|
-
|
|
213
|
-
1. **Never `--execute` without inspecting the dry-run first.** Worktree creation mutates the repo.
|
|
214
|
-
2. **Don't mix incompatible agents.** `release-captain` + `migration-guide` in the same swarm produces conflicting handoffs.
|
|
215
|
-
3. **Cap N at 8 for a single repo.** Past 8, worktree contention + pane crowding hurts.
|
|
216
|
-
4. **Never share tmux sessions.** Use `--session NAME` explicitly when running multiple swarms.
|
|
217
|
-
5. **Always human-merge handoffs.** Don't auto-apply changes from N parallel agents.
|
|
218
|
-
|
|
219
|
-
---
|
|
220
|
-
|
|
221
|
-
## Troubleshooting
|
|
222
|
-
|
|
223
|
-
**`tmux session already exists`** — pass `--replace` to tear it down, or `--session <new-name>`.
|
|
224
|
-
|
|
225
|
-
**`Worker X is missing a launcherCommand`** — pass `--harness <name>` or `--launcher-cmd "<template>"`.
|
|
226
|
-
|
|
227
|
-
**`seedPaths entries must stay inside repoRoot`** — the seed must be a subpath of the repo, not an absolute external path.
|
|
228
|
-
|
|
229
|
-
**Worktrees have stale state** — `--replace` cleans them. Or manually: `git worktree prune --expire now`.
|
|
230
|
-
|
|
231
|
-
**A worker stalled** — `tmux attach -t <session>` and inspect its pane. Or `cat .orchestration/<session>/<slug>/status.md`.
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
## Roadmap interactions
|
|
236
|
-
|
|
237
|
-
- **Phase 2.3 — local dashboard** will surface live swarm status (running / completed / failed per worker, latency, token usage).
|
|
238
|
-
- **Phase 2.6 — sandbox layer** will optionally Docker-isolate each worker.
|
|
239
|
-
- **Phase 2.8 — replay** will let you replay a finished swarm from its coordination files.
|
|
240
|
-
|
|
241
|
-
---
|
|
242
|
-
|
|
243
|
-
Built into [Kodelyth ECC](../README.md). MIT licensed.
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
"""Tests for grader module — compliance scoring with LLM classification."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from unittest.mock import patch
|
|
5
|
-
|
|
6
|
-
import pytest
|
|
7
|
-
|
|
8
|
-
from scripts.grader import ComplianceResult, StepResult, grade
|
|
9
|
-
from scripts.parser import parse_spec, parse_trace
|
|
10
|
-
|
|
11
|
-
FIXTURES = Path(__file__).parent.parent / "fixtures"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@pytest.fixture
|
|
15
|
-
def tdd_spec():
|
|
16
|
-
return parse_spec(FIXTURES / "tdd_spec.yaml")
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@pytest.fixture
|
|
20
|
-
def compliant_trace():
|
|
21
|
-
return parse_trace(FIXTURES / "compliant_trace.jsonl")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@pytest.fixture
|
|
25
|
-
def noncompliant_trace():
|
|
26
|
-
return parse_trace(FIXTURES / "noncompliant_trace.jsonl")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _mock_compliant_classification(spec, trace, model="haiku"): # noqa: ARG001
|
|
30
|
-
"""Simulate LLM correctly classifying a compliant trace."""
|
|
31
|
-
return {
|
|
32
|
-
"write_test": [0],
|
|
33
|
-
"run_test_red": [1],
|
|
34
|
-
"write_impl": [2],
|
|
35
|
-
"run_test_green": [3],
|
|
36
|
-
"refactor": [4],
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _mock_noncompliant_classification(spec, trace, model="haiku"):
|
|
41
|
-
"""Simulate LLM classifying a noncompliant trace (impl before test)."""
|
|
42
|
-
return {
|
|
43
|
-
"write_impl": [0], # src/fib.py written first
|
|
44
|
-
"write_test": [1], # test written second
|
|
45
|
-
"run_test_green": [2], # only a passing test run
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _mock_empty_classification(spec, trace, model="haiku"):
|
|
50
|
-
return {}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class TestGradeCompliant:
|
|
54
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_compliant_classification)
|
|
55
|
-
def test_returns_compliance_result(self, mock_cls, tdd_spec, compliant_trace) -> None:
|
|
56
|
-
result = grade(tdd_spec, compliant_trace)
|
|
57
|
-
assert isinstance(result, ComplianceResult)
|
|
58
|
-
|
|
59
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_compliant_classification)
|
|
60
|
-
def test_full_compliance(self, mock_cls, tdd_spec, compliant_trace) -> None:
|
|
61
|
-
result = grade(tdd_spec, compliant_trace)
|
|
62
|
-
assert result.compliance_rate == 1.0
|
|
63
|
-
|
|
64
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_compliant_classification)
|
|
65
|
-
def test_all_required_steps_detected(self, mock_cls, tdd_spec, compliant_trace) -> None:
|
|
66
|
-
result = grade(tdd_spec, compliant_trace)
|
|
67
|
-
required_results = [s for s in result.steps if s.step_id in
|
|
68
|
-
("write_test", "run_test_red", "write_impl", "run_test_green")]
|
|
69
|
-
assert all(s.detected for s in required_results)
|
|
70
|
-
|
|
71
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_compliant_classification)
|
|
72
|
-
def test_optional_step_detected(self, mock_cls, tdd_spec, compliant_trace) -> None:
|
|
73
|
-
result = grade(tdd_spec, compliant_trace)
|
|
74
|
-
refactor = next(s for s in result.steps if s.step_id == "refactor")
|
|
75
|
-
assert refactor.detected is True
|
|
76
|
-
|
|
77
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_compliant_classification)
|
|
78
|
-
def test_no_hook_promotion_recommended(self, mock_cls, tdd_spec, compliant_trace) -> None:
|
|
79
|
-
result = grade(tdd_spec, compliant_trace)
|
|
80
|
-
assert result.recommend_hook_promotion is False
|
|
81
|
-
|
|
82
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_compliant_classification)
|
|
83
|
-
def test_step_evidence_not_empty(self, mock_cls, tdd_spec, compliant_trace) -> None:
|
|
84
|
-
result = grade(tdd_spec, compliant_trace)
|
|
85
|
-
for step in result.steps:
|
|
86
|
-
if step.detected:
|
|
87
|
-
assert len(step.evidence) > 0
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class TestGradeNoncompliant:
|
|
91
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_noncompliant_classification)
|
|
92
|
-
def test_low_compliance(self, mock_cls, tdd_spec, noncompliant_trace) -> None:
|
|
93
|
-
result = grade(tdd_spec, noncompliant_trace)
|
|
94
|
-
assert result.compliance_rate < 1.0
|
|
95
|
-
|
|
96
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_noncompliant_classification)
|
|
97
|
-
def test_write_test_fails_ordering(self, mock_cls, tdd_spec, noncompliant_trace) -> None:
|
|
98
|
-
"""write_test has before_step=write_impl, but test is written AFTER impl."""
|
|
99
|
-
result = grade(tdd_spec, noncompliant_trace)
|
|
100
|
-
write_test = next(s for s in result.steps if s.step_id == "write_test")
|
|
101
|
-
assert write_test.detected is False
|
|
102
|
-
|
|
103
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_noncompliant_classification)
|
|
104
|
-
def test_run_test_red_not_detected(self, mock_cls, tdd_spec, noncompliant_trace) -> None:
|
|
105
|
-
result = grade(tdd_spec, noncompliant_trace)
|
|
106
|
-
run_red = next(s for s in result.steps if s.step_id == "run_test_red")
|
|
107
|
-
assert run_red.detected is False
|
|
108
|
-
|
|
109
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_noncompliant_classification)
|
|
110
|
-
def test_hook_promotion_recommended(self, mock_cls, tdd_spec, noncompliant_trace) -> None:
|
|
111
|
-
result = grade(tdd_spec, noncompliant_trace)
|
|
112
|
-
assert result.recommend_hook_promotion is True
|
|
113
|
-
|
|
114
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_noncompliant_classification)
|
|
115
|
-
def test_failure_reasons_present(self, mock_cls, tdd_spec, noncompliant_trace) -> None:
|
|
116
|
-
result = grade(tdd_spec, noncompliant_trace)
|
|
117
|
-
failed_steps = [s for s in result.steps if not s.detected and s.step_id != "refactor"]
|
|
118
|
-
for step in failed_steps:
|
|
119
|
-
assert step.failure_reason is not None
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
class TestGradeEdgeCases:
|
|
123
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_empty_classification)
|
|
124
|
-
def test_empty_trace(self, mock_cls, tdd_spec) -> None:
|
|
125
|
-
result = grade(tdd_spec, [])
|
|
126
|
-
assert result.compliance_rate == 0.0
|
|
127
|
-
assert result.recommend_hook_promotion is True
|
|
128
|
-
|
|
129
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_compliant_classification)
|
|
130
|
-
def test_compliance_rate_is_ratio_of_required_only(self, mock_cls, tdd_spec, compliant_trace) -> None:
|
|
131
|
-
result = grade(tdd_spec, compliant_trace)
|
|
132
|
-
assert result.compliance_rate == 1.0
|
|
133
|
-
|
|
134
|
-
@patch("scripts.grader.classify_events", side_effect=_mock_compliant_classification)
|
|
135
|
-
def test_spec_id_in_result(self, mock_cls, tdd_spec, compliant_trace) -> None:
|
|
136
|
-
result = grade(tdd_spec, compliant_trace)
|
|
137
|
-
assert result.spec_id == "tdd-workflow"
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"""Tests for parser module — JSONL trace and YAML spec parsing."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from scripts.parser import (
|
|
8
|
-
ComplianceSpec,
|
|
9
|
-
Detector,
|
|
10
|
-
ObservationEvent,
|
|
11
|
-
Step,
|
|
12
|
-
parse_spec,
|
|
13
|
-
parse_trace,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
FIXTURES = Path(__file__).parent.parent / "fixtures"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TestParseTrace:
|
|
20
|
-
def test_parses_compliant_trace(self) -> None:
|
|
21
|
-
events = parse_trace(FIXTURES / "compliant_trace.jsonl")
|
|
22
|
-
assert len(events) == 5
|
|
23
|
-
assert all(isinstance(e, ObservationEvent) for e in events)
|
|
24
|
-
|
|
25
|
-
def test_events_sorted_by_timestamp(self) -> None:
|
|
26
|
-
events = parse_trace(FIXTURES / "compliant_trace.jsonl")
|
|
27
|
-
timestamps = [e.timestamp for e in events]
|
|
28
|
-
assert timestamps == sorted(timestamps)
|
|
29
|
-
|
|
30
|
-
def test_event_fields(self) -> None:
|
|
31
|
-
events = parse_trace(FIXTURES / "compliant_trace.jsonl")
|
|
32
|
-
first = events[0]
|
|
33
|
-
assert first.tool == "Write"
|
|
34
|
-
assert first.session == "sess-001"
|
|
35
|
-
assert "test_fib.py" in first.input
|
|
36
|
-
assert first.output == "File created"
|
|
37
|
-
|
|
38
|
-
def test_parses_noncompliant_trace(self) -> None:
|
|
39
|
-
events = parse_trace(FIXTURES / "noncompliant_trace.jsonl")
|
|
40
|
-
assert len(events) == 3
|
|
41
|
-
assert "src/fib.py" in events[0].input
|
|
42
|
-
|
|
43
|
-
def test_empty_file_returns_empty_list(self, tmp_path: Path) -> None:
|
|
44
|
-
empty = tmp_path / "empty.jsonl"
|
|
45
|
-
empty.write_text("")
|
|
46
|
-
events = parse_trace(empty)
|
|
47
|
-
assert events == []
|
|
48
|
-
|
|
49
|
-
def test_nonexistent_file_raises(self) -> None:
|
|
50
|
-
with pytest.raises(FileNotFoundError):
|
|
51
|
-
parse_trace(Path("/nonexistent/trace.jsonl"))
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class TestParseSpec:
|
|
55
|
-
def test_parses_tdd_spec(self) -> None:
|
|
56
|
-
spec = parse_spec(FIXTURES / "tdd_spec.yaml")
|
|
57
|
-
assert isinstance(spec, ComplianceSpec)
|
|
58
|
-
assert spec.id == "tdd-workflow"
|
|
59
|
-
assert len(spec.steps) == 5
|
|
60
|
-
|
|
61
|
-
def test_step_fields(self) -> None:
|
|
62
|
-
spec = parse_spec(FIXTURES / "tdd_spec.yaml")
|
|
63
|
-
first = spec.steps[0]
|
|
64
|
-
assert isinstance(first, Step)
|
|
65
|
-
assert first.id == "write_test"
|
|
66
|
-
assert first.required is True
|
|
67
|
-
assert isinstance(first.detector, Detector)
|
|
68
|
-
assert "test file" in first.detector.description
|
|
69
|
-
assert first.detector.before_step == "write_impl"
|
|
70
|
-
|
|
71
|
-
def test_optional_detector_fields(self) -> None:
|
|
72
|
-
spec = parse_spec(FIXTURES / "tdd_spec.yaml")
|
|
73
|
-
write_test = spec.steps[0]
|
|
74
|
-
assert write_test.detector.after_step is None
|
|
75
|
-
|
|
76
|
-
run_test_red = spec.steps[1]
|
|
77
|
-
assert run_test_red.detector.after_step == "write_test"
|
|
78
|
-
assert run_test_red.detector.before_step == "write_impl"
|
|
79
|
-
|
|
80
|
-
def test_scoring_threshold(self) -> None:
|
|
81
|
-
spec = parse_spec(FIXTURES / "tdd_spec.yaml")
|
|
82
|
-
assert spec.threshold_promote_to_hook == 0.6
|
|
83
|
-
|
|
84
|
-
def test_required_vs_optional_steps(self) -> None:
|
|
85
|
-
spec = parse_spec(FIXTURES / "tdd_spec.yaml")
|
|
86
|
-
required = [s for s in spec.steps if s.required]
|
|
87
|
-
optional = [s for s in spec.steps if not s.required]
|
|
88
|
-
assert len(required) == 4
|
|
89
|
-
assert len(optional) == 1
|
|
90
|
-
assert optional[0].id == "refactor"
|
package/social/card-agents.svg
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 420" width="700" height="420" font-family="'Segoe UI', system-ui, -apple-system, sans-serif">
|
|
2
|
-
<defs>
|
|
3
|
-
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
-
<stop offset="0%" style="stop-color:#050811"/>
|
|
5
|
-
<stop offset="100%" style="stop-color:#0d1117"/>
|
|
6
|
-
</linearGradient>
|
|
7
|
-
<linearGradient id="mem" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
8
|
-
<stop offset="0%" style="stop-color:#059669"/>
|
|
9
|
-
<stop offset="100%" style="stop-color:#065f46"/>
|
|
10
|
-
</linearGradient>
|
|
11
|
-
</defs>
|
|
12
|
-
|
|
13
|
-
<rect width="700" height="420" fill="url(#bg)" rx="12"/>
|
|
14
|
-
<rect width="700" height="420" fill="none" stroke="#21262d" stroke-width="1" rx="12"/>
|
|
15
|
-
|
|
16
|
-
<!-- Header -->
|
|
17
|
-
<text x="28" y="42" font-size="16" font-weight="800" fill="#ffffff">70 Agents — The Complete Roster</text>
|
|
18
|
-
<rect x="28" y="54" width="644" height="1" fill="#21262d"/>
|
|
19
|
-
|
|
20
|
-
<!-- Row 1 -->
|
|
21
|
-
<rect x="28" y="68" width="196" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
22
|
-
<rect x="28" y="68" width="2.5" height="56" rx="1" fill="#a78bfa"/>
|
|
23
|
-
<text x="40" y="88" font-size="11" font-weight="700" fill="#ffffff">kodelyth-advisor</text>
|
|
24
|
-
<text x="40" y="105" font-size="10" fill="#484f58">lost / overwhelmed</text>
|
|
25
|
-
|
|
26
|
-
<rect x="234" y="68" width="196" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
27
|
-
<rect x="234" y="68" width="2.5" height="56" rx="1" fill="#a78bfa"/>
|
|
28
|
-
<text x="246" y="88" font-size="11" font-weight="700" fill="#ffffff">pair-programmer</text>
|
|
29
|
-
<text x="246" y="105" font-size="10" fill="#484f58">before you write code</text>
|
|
30
|
-
|
|
31
|
-
<rect x="440" y="68" width="232" height="56" rx="6" fill="#0d2b1d" stroke="#059669" stroke-opacity="0.4" stroke-width="1"/>
|
|
32
|
-
<rect x="440" y="68" width="2.5" height="56" rx="1" fill="url(#mem)"/>
|
|
33
|
-
<rect x="568" y="72" width="36" height="14" rx="7" fill="#059669" fill-opacity="0.3" stroke="#059669" stroke-opacity="0.5" stroke-width="1"/>
|
|
34
|
-
<text x="586" y="83" font-size="8" fill="#10b981" text-anchor="middle" font-weight="700">ECC</text>
|
|
35
|
-
<text x="452" y="88" font-size="11" font-weight="700" fill="#ffffff">kodelyth-memory</text>
|
|
36
|
-
<text x="452" y="105" font-size="10" fill="#484f58">local BM25 self-learning</text>
|
|
37
|
-
|
|
38
|
-
<!-- Row 2 -->
|
|
39
|
-
<rect x="28" y="140" width="196" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
40
|
-
<rect x="28" y="140" width="2.5" height="56" rx="1" fill="#ef4444"/>
|
|
41
|
-
<text x="40" y="160" font-size="11" font-weight="700" fill="#ffffff">debug-detective</text>
|
|
42
|
-
<text x="40" y="177" font-size="10" fill="#484f58">root cause, never guesses</text>
|
|
43
|
-
|
|
44
|
-
<rect x="234" y="140" width="196" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
45
|
-
<rect x="234" y="140" width="2.5" height="56" rx="1" fill="#f97316"/>
|
|
46
|
-
<text x="246" y="160" font-size="11" font-weight="700" fill="#ffffff">silent-failure-hunter</text>
|
|
47
|
-
<text x="246" y="177" font-size="10" fill="#484f58">bugs with no error</text>
|
|
48
|
-
|
|
49
|
-
<rect x="440" y="140" width="232" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
50
|
-
<rect x="440" y="140" width="2.5" height="56" rx="1" fill="#ef4444"/>
|
|
51
|
-
<text x="452" y="160" font-size="11" font-weight="700" fill="#ffffff">incident-commander</text>
|
|
52
|
-
<text x="452" y="177" font-size="10" fill="#484f58">P0/P1 production triage</text>
|
|
53
|
-
|
|
54
|
-
<!-- Row 3 -->
|
|
55
|
-
<rect x="28" y="212" width="196" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
56
|
-
<rect x="28" y="212" width="2.5" height="56" rx="1" fill="#2563eb"/>
|
|
57
|
-
<text x="40" y="232" font-size="11" font-weight="700" fill="#ffffff">security-reviewer</text>
|
|
58
|
-
<text x="40" y="249" font-size="10" fill="#484f58">OWASP, auth, secrets</text>
|
|
59
|
-
|
|
60
|
-
<rect x="234" y="212" width="196" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
61
|
-
<rect x="234" y="212" width="2.5" height="56" rx="1" fill="#2563eb"/>
|
|
62
|
-
<text x="246" y="232" font-size="11" font-weight="700" fill="#ffffff">api-guardian</text>
|
|
63
|
-
<text x="246" y="249" font-size="10" fill="#484f58">breaking change detection</text>
|
|
64
|
-
|
|
65
|
-
<rect x="440" y="212" width="232" height="56" rx="6" fill="#0d2b1d" stroke="#059669" stroke-opacity="0.4" stroke-width="1"/>
|
|
66
|
-
<rect x="440" y="212" width="2.5" height="56" rx="1" fill="url(#mem)"/>
|
|
67
|
-
<rect x="568" y="216" width="46" height="14" rx="7" fill="#059669" fill-opacity="0.3" stroke="#059669" stroke-opacity="0.5" stroke-width="1"/>
|
|
68
|
-
<text x="591" y="227" font-size="8" fill="#10b981" text-anchor="middle" font-weight="700">ECC</text>
|
|
69
|
-
<text x="452" y="232" font-size="11" font-weight="700" fill="#ffffff">image-architect</text>
|
|
70
|
-
<text x="452" y="249" font-size="10" fill="#484f58">Gemini / DALL-E / SVG</text>
|
|
71
|
-
|
|
72
|
-
<!-- Row 4 -->
|
|
73
|
-
<rect x="28" y="284" width="196" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
74
|
-
<rect x="28" y="284" width="2.5" height="56" rx="1" fill="#f59e0b"/>
|
|
75
|
-
<text x="40" y="304" font-size="11" font-weight="700" fill="#ffffff">performance-optimizer</text>
|
|
76
|
-
<text x="40" y="321" font-size="10" fill="#484f58">bottlenecks, N+1, bundle</text>
|
|
77
|
-
|
|
78
|
-
<rect x="234" y="284" width="196" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
79
|
-
<rect x="234" y="284" width="2.5" height="56" rx="1" fill="#f59e0b"/>
|
|
80
|
-
<text x="246" y="304" font-size="11" font-weight="700" fill="#ffffff">load-tester</text>
|
|
81
|
-
<text x="246" y="321" font-size="10" fill="#484f58">k6, Locust, capacity plan</text>
|
|
82
|
-
|
|
83
|
-
<rect x="440" y="284" width="232" height="56" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
84
|
-
<rect x="440" y="284" width="2.5" height="56" rx="1" fill="#22d3ee"/>
|
|
85
|
-
<text x="452" y="304" font-size="11" font-weight="700" fill="#ffffff">migration-guide</text>
|
|
86
|
-
<text x="452" y="321" font-size="10" fill="#484f58">React / Next / Node upgrades</text>
|
|
87
|
-
|
|
88
|
-
<!-- Footer -->
|
|
89
|
-
<rect x="28" y="356" width="644" height="44" rx="6" fill="#161b22" stroke="#21262d" stroke-width="1"/>
|
|
90
|
-
<text x="350" y="373" font-size="11" fill="#484f58" text-anchor="middle">+ 50 more: language reviewers, build resolvers, tdd-guide, refactor-cleaner, ux-reviewer, git-rescue ...</text>
|
|
91
|
-
<text x="350" y="390" font-size="11" fill="#484f58" text-anchor="middle">All agents load automatically via semantic intent routing — no command names needed</text>
|
|
92
|
-
|
|
93
|
-
<!-- Ghost Depth Mark — brand stamp (scale 0.45×) -->
|
|
94
|
-
<g transform="translate(606, 120) scale(0.45)" opacity="0.16">
|
|
95
|
-
<polyline points="-44,4 12,100 -44,196"
|
|
96
|
-
stroke="#ffffff" stroke-width="10"
|
|
97
|
-
stroke-linecap="round" stroke-linejoin="round"
|
|
98
|
-
fill="none"/>
|
|
99
|
-
<polyline points="0,0 88,100 0,200"
|
|
100
|
-
stroke="#ffffff" stroke-width="32"
|
|
101
|
-
stroke-linecap="round" stroke-linejoin="round"
|
|
102
|
-
fill="none"/>
|
|
103
|
-
</g>
|
|
104
|
-
</svg>
|
package/social/card-install.svg
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 360" width="640" height="360" font-family="'Segoe UI', system-ui, -apple-system, sans-serif">
|
|
2
|
-
<defs>
|
|
3
|
-
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
-
<stop offset="0%" style="stop-color:#050811"/>
|
|
5
|
-
<stop offset="100%" style="stop-color:#0d1117"/>
|
|
6
|
-
</linearGradient>
|
|
7
|
-
<linearGradient id="termGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
8
|
-
<stop offset="0%" style="stop-color:#161b22"/>
|
|
9
|
-
<stop offset="100%" style="stop-color:#0d1117"/>
|
|
10
|
-
</linearGradient>
|
|
11
|
-
</defs>
|
|
12
|
-
|
|
13
|
-
<rect width="640" height="360" fill="url(#bg)" rx="12"/>
|
|
14
|
-
<rect width="640" height="360" fill="none" stroke="#21262d" stroke-width="1" rx="12"/>
|
|
15
|
-
|
|
16
|
-
<!-- Title section -->
|
|
17
|
-
<rect x="28" y="24" width="72" height="20" rx="10" fill="#059669" fill-opacity="0.15" stroke="#059669" stroke-opacity="0.4" stroke-width="1"/>
|
|
18
|
-
<text x="64" y="38" font-size="9" fill="#10b981" text-anchor="middle" font-weight="700" letter-spacing="0.5">INSTALL</text>
|
|
19
|
-
|
|
20
|
-
<text x="28" y="72" font-size="24" font-weight="800" fill="#ffffff">One command. All platforms.</text>
|
|
21
|
-
<text x="28" y="96" font-size="13" fill="#6e7681">Works on Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode</text>
|
|
22
|
-
|
|
23
|
-
<line x1="28" y1="112" x2="612" y2="112" stroke="#21262d" stroke-width="1"/>
|
|
24
|
-
|
|
25
|
-
<!-- Terminal -->
|
|
26
|
-
<rect x="28" y="124" width="584" height="180" rx="8" fill="url(#termGrad)" stroke="#21262d" stroke-width="1"/>
|
|
27
|
-
<rect x="28" y="124" width="584" height="26" rx="8" fill="#161b22"/>
|
|
28
|
-
<rect x="28" y="140" width="584" height="10" fill="#161b22"/>
|
|
29
|
-
<circle cx="48" cy="137" r="4" fill="#ff5f57"/>
|
|
30
|
-
<circle cx="62" cy="137" r="4" fill="#febc2e"/>
|
|
31
|
-
<circle cx="76" cy="137" r="4" fill="#28c840"/>
|
|
32
|
-
<text x="320" y="141" font-size="10" fill="#484f58" text-anchor="middle">terminal</text>
|
|
33
|
-
|
|
34
|
-
<text x="48" y="168" font-family="'Courier New', Courier, monospace" font-size="12" fill="#10b981">$</text>
|
|
35
|
-
<text x="60" y="168" font-family="'Courier New', Courier, monospace" font-size="12" fill="#e6edf3"> npx kodelyth-ecc</text>
|
|
36
|
-
<text x="48" y="184" font-family="'Courier New', Courier, monospace" font-size="11" fill="#484f58"> # or for Windsurf:</text>
|
|
37
|
-
<text x="48" y="200" font-family="'Courier New', Courier, monospace" font-size="12" fill="#e6edf3"> npx kodelyth-ecc --target windsurf-project</text>
|
|
38
|
-
<text x="48" y="216" font-family="'Courier New', Courier, monospace" font-size="11" fill="#484f58"> # or Antigravity:</text>
|
|
39
|
-
<text x="48" y="232" font-family="'Courier New', Courier, monospace" font-size="12" fill="#e6edf3"> npx kodelyth-ecc --target antigravity</text>
|
|
40
|
-
<text x="48" y="256" font-family="'Courier New', Courier, monospace" font-size="11" fill="#10b981"> Installing v1.8.0 — 70 agents, 194 skills, 97 commands...</text>
|
|
41
|
-
<text x="48" y="292" font-family="'Courier New', Courier, monospace" font-size="11" fill="#10b981"> Done. Type /kodelyth-quickstart to begin.</text>
|
|
42
|
-
|
|
43
|
-
<!-- Footer -->
|
|
44
|
-
<text x="28" y="332" font-size="11" fill="#484f58">Node.js 18+ required</text>
|
|
45
|
-
<text x="612" y="332" font-size="11" fill="#484f58" text-anchor="end">github.com/sifxprime/kodelyth-ecc</text>
|
|
46
|
-
|
|
47
|
-
<!-- Ghost Depth Mark — brand stamp (scale 0.4×) -->
|
|
48
|
-
<g transform="translate(550, 100) scale(0.4)" opacity="0.16">
|
|
49
|
-
<polyline points="-44,4 12,100 -44,196"
|
|
50
|
-
stroke="#ffffff" stroke-width="10"
|
|
51
|
-
stroke-linecap="round" stroke-linejoin="round"
|
|
52
|
-
fill="none"/>
|
|
53
|
-
<polyline points="0,0 88,100 0,200"
|
|
54
|
-
stroke="#ffffff" stroke-width="32"
|
|
55
|
-
stroke-linecap="round" stroke-linejoin="round"
|
|
56
|
-
fill="none"/>
|
|
57
|
-
</g>
|
|
58
|
-
</svg>
|