ndomo 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/.bun-version +1 -0
- package/.dockerignore +79 -0
- package/.editorconfig +18 -0
- package/.env.example +19 -0
- package/.github/CODEOWNERS +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +62 -0
- package/.github/ISSUE_TEMPLATE/config.yml +2 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +34 -0
- package/.github/dependabot.yml +36 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/release.yml +30 -0
- package/.github/workflows/gitleaks.yml +28 -0
- package/.github/workflows/release-please.yml +27 -0
- package/.github/workflows/smoke.yml +29 -0
- package/.husky/commit-msg +1 -0
- package/CHANGELOG.md +114 -0
- package/Dockerfile +32 -0
- package/README.es.md +174 -0
- package/README.md +187 -0
- package/agents/chronicler.md +98 -0
- package/agents/ci-smith.md +136 -0
- package/agents/craftsman.md +341 -0
- package/agents/deploy-smith.md +138 -0
- package/agents/foreman.md +377 -0
- package/agents/go-smith.md +164 -0
- package/agents/guild.md +188 -0
- package/agents/inspector.md +83 -0
- package/agents/js-smith.md +127 -0
- package/agents/ops-scout.md +173 -0
- package/agents/painter.md +200 -0
- package/agents/python-smith.md +120 -0
- package/agents/ranger.md +307 -0
- package/agents/release-smith.md +165 -0
- package/agents/rust-smith.md +159 -0
- package/agents/sage.md +178 -0
- package/agents/scout.md +144 -0
- package/agents/scribe.md +156 -0
- package/agents/smith.md +201 -0
- package/agents/vue-smith.md +155 -0
- package/agents/warden.md +216 -0
- package/agents/zig-smith.md +156 -0
- package/bin/ndomo-analyses.ts +4 -0
- package/bin/ndomo-status.ts +4 -0
- package/biome.json +57 -0
- package/bun.lock +514 -0
- package/commitlint.config.js +3 -0
- package/config/ndomo.config.json +258 -0
- package/config/ndomo.schema.json +166 -0
- package/docs/agents.md +375 -0
- package/docs/bugs/plan-create-orphan-fk.md +131 -0
- package/docs/bugs/task_create_batch-order-index-collision.md +158 -0
- package/docs/configuration.md +276 -0
- package/docs/database.md +364 -0
- package/docs/features/feature-flexible-builder-v1.md +724 -0
- package/docs/features/feature-flexible-builder-v2.md +882 -0
- package/docs/features/feature-flexible-builder.md +974 -0
- package/docs/http-server.md +244 -0
- package/docs/installation.md +259 -0
- package/docs/integrations.md +129 -0
- package/docs/operations/anti-pattern-sub-agent-verify-2026-06-21.md +32 -0
- package/docs/operations/audit-v1.md +417 -0
- package/docs/operations/audit-v2.md +197 -0
- package/docs/operations/audit-v3.md +306 -0
- package/docs/operations/db-optimize-foundations.md +123 -0
- package/docs/operations/verify-gate-architecture.md +82 -0
- package/docs/workflows.md +448 -0
- package/opencode.json +5 -0
- package/package.json +65 -0
- package/release-please-config.json +11 -0
- package/scripts/dev-bust-cache.sh +164 -0
- package/scripts/install.sh +688 -0
- package/scripts/smoke-e2e.ts +704 -0
- package/scripts/smoke-hot.ts +417 -0
- package/scripts/smoke-http.sh +228 -0
- package/scripts/smoke-v4.ts +256 -0
- package/scripts/smoke-v5.ts +397 -0
- package/scripts/smoke.sh +9 -0
- package/scripts/uninstall.sh +224 -0
- package/skills/api-security-best-practices/SKILL.md +915 -0
- package/skills/bash-scripting/SKILL.md +201 -0
- package/skills/bun/SKILL.md +313 -0
- package/skills/cavecrew/SKILL.md +82 -0
- package/skills/caveman/SKILL.md +74 -0
- package/skills/caveman-review/README.md +33 -0
- package/skills/caveman-review/SKILL.md +55 -0
- package/skills/find-skills/SKILL.md +142 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +55 -0
- package/skills/golang-patterns/SKILL.md +674 -0
- package/skills/golang-security/SKILL.md +185 -0
- package/skills/golang-security/evals/evals.json +595 -0
- package/skills/golang-security/references/architecture.md +268 -0
- package/skills/golang-security/references/checklist.md +80 -0
- package/skills/golang-security/references/cookies.md +200 -0
- package/skills/golang-security/references/cryptography.md +424 -0
- package/skills/golang-security/references/filesystem.md +285 -0
- package/skills/golang-security/references/injection.md +315 -0
- package/skills/golang-security/references/logging.md +163 -0
- package/skills/golang-security/references/memory-safety.md +241 -0
- package/skills/golang-security/references/network.md +253 -0
- package/skills/golang-security/references/secrets.md +189 -0
- package/skills/golang-security/references/third-party.md +159 -0
- package/skills/golang-security/references/threat-modeling.md +189 -0
- package/skills/golang-testing/SKILL.md +720 -0
- package/skills/grill-me/SKILL.md +7 -0
- package/skills/javascript-testing-patterns/SKILL.md +537 -0
- package/skills/javascript-testing-patterns/references/advanced-testing-patterns.md +513 -0
- package/skills/modern-javascript-patterns/SKILL.md +43 -0
- package/skills/modern-javascript-patterns/references/advanced-patterns.md +487 -0
- package/skills/modern-javascript-patterns/references/details.md +457 -0
- package/skills/python-anti-patterns/SKILL.md +349 -0
- package/skills/python-design-patterns/SKILL.md +85 -0
- package/skills/python-design-patterns/references/details.md +353 -0
- package/skills/python-error-handling/SKILL.md +193 -0
- package/skills/python-error-handling/references/details.md +171 -0
- package/skills/python-testing-patterns/SKILL.md +278 -0
- package/skills/python-testing-patterns/references/advanced-patterns.md +411 -0
- package/skills/python-testing-patterns/references/details.md +349 -0
- package/skills/rust-patterns/SKILL.md +500 -0
- package/skills/rust-testing/SKILL.md +501 -0
- package/skills/security-review/SKILL.md +504 -0
- package/skills/security-review/cloud-infrastructure-security.md +361 -0
- package/skills/vue-best-practices/SKILL.md +154 -0
- package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue-best-practices/references/component-data-flow.md +307 -0
- package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue-best-practices/references/composables.md +290 -0
- package/skills/vue-best-practices/references/directives.md +162 -0
- package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue-best-practices/references/reactivity.md +344 -0
- package/skills/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/zig-0.16/SKILL.md +840 -0
- package/skills/zig-0.16/scripts/check-zig-version.sh +21 -0
- package/src/cli/analyses.ts +280 -0
- package/src/cli/index.ts +108 -0
- package/src/cli/serve.ts +192 -0
- package/src/cli/smoke.ts +131 -0
- package/src/cli/status.test.ts +204 -0
- package/src/cli/status.ts +263 -0
- package/src/cli/vacuum.test.ts +82 -0
- package/src/cli/vacuum.ts +96 -0
- package/src/config/schema.test.ts +88 -0
- package/src/config/schema.ts +64 -0
- package/src/db/analyses-migration.test.ts +210 -0
- package/src/db/analyses.test.ts +466 -0
- package/src/db/analyses.ts +375 -0
- package/src/db/auto-checkpoint.ts +131 -0
- package/src/db/client.test.ts +129 -0
- package/src/db/client.ts +55 -0
- package/src/db/fts-escape.ts +20 -0
- package/src/db/incidents.test.ts +201 -0
- package/src/db/incidents.ts +93 -0
- package/src/db/index.ts +86 -0
- package/src/db/migrations-v13.test.ts +141 -0
- package/src/db/migrations-v8.test.ts +301 -0
- package/src/db/migrations.ts +147 -0
- package/src/db/plan-archive.test.ts +180 -0
- package/src/db/plan-archive.ts +274 -0
- package/src/db/plan-create.test.ts +276 -0
- package/src/db/plan-create.ts +78 -0
- package/src/db/plan-files.test.ts +289 -0
- package/src/db/plan-update-status.ts +287 -0
- package/src/db/plans.test.ts +490 -0
- package/src/db/plans.ts +534 -0
- package/src/db/resolve-project-dir.test.ts +143 -0
- package/src/db/resolve-project-dir.ts +75 -0
- package/src/db/rollbacks.test.ts +150 -0
- package/src/db/rollbacks.ts +67 -0
- package/src/db/schema.ts +907 -0
- package/src/db/sessions.test.ts +80 -0
- package/src/db/sessions.ts +135 -0
- package/src/db/shutdown.test.ts +147 -0
- package/src/db/shutdown.ts +45 -0
- package/src/db/tasks.test.ts +921 -0
- package/src/db/tasks.ts +747 -0
- package/src/db/types.ts +619 -0
- package/src/http/__tests__/auth.test.ts +196 -0
- package/src/http/__tests__/routes.test.ts +465 -0
- package/src/http/__tests__/sse.test.ts +317 -0
- package/src/http/auth.ts +72 -0
- package/src/http/middleware/cors.ts +53 -0
- package/src/http/middleware/security-headers.ts +21 -0
- package/src/http/routes/events.ts +112 -0
- package/src/http/routes/health.ts +51 -0
- package/src/http/routes/plans.ts +66 -0
- package/src/http/routes/sessions.ts +50 -0
- package/src/http/routes/tasks.ts +60 -0
- package/src/http/server.ts +95 -0
- package/src/http/sse.ts +116 -0
- package/src/index.ts +37 -0
- package/src/lib.ts +65 -0
- package/src/mem/scoped.ts +65 -0
- package/src/orchestrator/background.test.ts +268 -0
- package/src/orchestrator/background.ts +293 -0
- package/src/orchestrator/memory-hook.ts +182 -0
- package/src/orchestrator/reconciler.ts +123 -0
- package/src/orchestrator/scheduler.test.ts +300 -0
- package/src/orchestrator/scheduler.ts +243 -0
- package/src/plugin.test.ts +2574 -0
- package/src/plugin.ts +1690 -0
- package/src/sdk/client.ts +66 -0
- package/src/worktrees/manager.ts +236 -0
- package/src/worktrees/state.ts +87 -0
- package/tests/integration/ranger-flow.test.ts +257 -0
- package/tools/analysis_archive.ts +28 -0
- package/tools/analysis_create.ts +55 -0
- package/tools/analysis_get.ts +33 -0
- package/tools/analysis_link_plan.ts +44 -0
- package/tools/analysis_list.ts +48 -0
- package/tools/analysis_search.ts +36 -0
- package/tools/analysis_update.ts +44 -0
- package/tools/plan_approve.ts +31 -0
- package/tools/plan_create.ts +58 -0
- package/tools/plan_get.ts +40 -0
- package/tools/plan_list.ts +37 -0
- package/tools/plan_search.ts +34 -0
- package/tools/plan_update_status.ts +71 -0
- package/tools/session_checkpoint.ts +31 -0
- package/tools/session_end.ts +26 -0
- package/tools/session_start.ts +43 -0
- package/tools/task_create_batch.ts +70 -0
- package/tools/task_list.ts +35 -0
- package/tools/task_next_for_agent.ts +30 -0
- package/tools/task_search.ts +34 -0
- package/tools/task_update_status.ts +37 -0
- package/tsconfig.json +31 -0
package/docs/agents.md
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# Agent Reference
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
ndomo defines 22 agents grouped by function (4 primaries + 18 subagents). All agent definitions live as Markdown files in `agents/` with YAML frontmatter specifying model, temperature, permissions, and mode.
|
|
6
|
+
|
|
7
|
+
## Primaries
|
|
8
|
+
|
|
9
|
+
Four primary agents operate independently. The user switches between them manually via TUI.
|
|
10
|
+
|
|
11
|
+
### foreman
|
|
12
|
+
|
|
13
|
+
**Planner puro.** Analiza, explora, planifica y persiste planes en DB. No implementa. Solo planifica; la ejecución es del craftsman.
|
|
14
|
+
|
|
15
|
+
- **Model:** minimax/MiniMax-M3 (default), deepseek-v4-flash (budget)
|
|
16
|
+
- **Temperature:** 0.3
|
|
17
|
+
- **Permissions:** edit (ask), write (ask), bash (ask), question (allow)
|
|
18
|
+
- **Delegates to:** scout, scribe, sage, guild (exploración y análisis únicamente)
|
|
19
|
+
- **File:** `agents/foreman.md`
|
|
20
|
+
|
|
21
|
+
Flow (4 pasos):
|
|
22
|
+
1. **Aclaración** — identifica intención, pregunta si ambigüo, sugiere craftsman si ≤5 archivos
|
|
23
|
+
2. **Exploración** — memory search + scout/scribe/sage/guild según necesidad
|
|
24
|
+
3. **Plan Atómico** — desglosa en ≤5 steps con archivos esperados y dependencias
|
|
25
|
+
4. **Persistir** — `plan_create` + `task_create_batch` en DB
|
|
26
|
+
|
|
27
|
+
### craftsman
|
|
28
|
+
|
|
29
|
+
**Implementador artesano.** Primary agent para bugs, features pequeñas y refactors acotados. Opera en 4 estados según alcance.
|
|
30
|
+
|
|
31
|
+
- **Model:** opencode-go/deepseek-v4-flash
|
|
32
|
+
- **Temperature:** 0.1
|
|
33
|
+
- **Permissions:** edit (allow), write (allow), bash (ask con permit list), question (allow)
|
|
34
|
+
- **Delegates to:** smiths, painter, chronicler, inspector, scout, scribe (por routing interno)
|
|
35
|
+
- **File:** `agents/craftsman.md`
|
|
36
|
+
|
|
37
|
+
**Cuándo usar:**
|
|
38
|
+
- **Ad-hoc (cambio directo):** Tareas ≤5 archivos, bien definidas — cambiar a craftsman en TUI sin pasar por foreman
|
|
39
|
+
- **Plan formal:** Foreman creó un plan con tasks asignadas a `craftsman` — craftsman lee `plan_get`/`task_next_for_agent` y ejecuta
|
|
40
|
+
- **Diferencia con foreman:** Foreman planifica (solo DB writes), craftsman implementa (code + DB writes). Foreman NO delega a smiths; craftsman sí.
|
|
41
|
+
|
|
42
|
+
**Threshold 2/5/1:**
|
|
43
|
+
| Estado | Archivos | Comportamiento |
|
|
44
|
+
|--------|----------|----------------|
|
|
45
|
+
| 1 — Trivial | ≤2 | Implementación directa, sin `plan_db` |
|
|
46
|
+
| 2 — Multi-archivo acotado | 3-5 | Crea `plan_create` + `task_create_batch` propio |
|
|
47
|
+
| 3 — Plan formal | — | Lee plan existente del foreman y ejecuta |
|
|
48
|
+
| 4 — Fuera de dominio | >5 | **Rechaza** — "fuera de mi dominio" → cambiar a foreman |
|
|
49
|
+
|
|
50
|
+
**Routing interno (por extensión de archivo):**
|
|
51
|
+
| Extensión / Contexto | Sub-agente |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `.go` | `go-smith` |
|
|
54
|
+
| `.vue` / `.svelte` | `vue-smith` |
|
|
55
|
+
| `.ts` / `.tsx` / `.js` / `.jsx` | `js-smith` |
|
|
56
|
+
| `.py` | `python-smith` |
|
|
57
|
+
| `.rs` | `rust-smith` |
|
|
58
|
+
| `.zig` | `zig-smith` |
|
|
59
|
+
| UI/design + `type=design` | `painter` (solo vía craftsman) |
|
|
60
|
+
| Documentación / markdown | `chronicler` |
|
|
61
|
+
| Auditoría / seguridad | `inspector` |
|
|
62
|
+
| Sin match | `smith` (genérico) |
|
|
63
|
+
|
|
64
|
+
`task.metadata.stack` actúa como override explícito sobre la extensión. Si no hay match, usa `smith`.
|
|
65
|
+
|
|
66
|
+
**Painter solo craftsman:** El agente UI/UX `painter` solo es invocable desde craftsman, no desde foreman. Craftsman decide: painter si `stack="vue"` y `type="design"`, o vue-smith para implementación lógica.
|
|
67
|
+
|
|
68
|
+
### warden
|
|
69
|
+
|
|
70
|
+
**Custodio de operaciones.** Primary agent para CI/CD, deploy, releases, monitoring, secrets, branch strategy y security ops. Opera en paralelo con foreman (planificación) y craftsman (implementación) — su dominio es exclusivamente ops.
|
|
71
|
+
|
|
72
|
+
- **Model:** opencode-go/deepseek-v4-flash
|
|
73
|
+
- **Temperature:** 0.3
|
|
74
|
+
- **Permissions:** edit (ask), write (ask), bash (ask con read-only allow), webfetch (allow), question (allow), task (allow)
|
|
75
|
+
- **Delegates to:** ci-smith, deploy-smith, release-smith, ops-scout (own fleet), sage, inspector (reused)
|
|
76
|
+
- **File:** `agents/warden.md`
|
|
77
|
+
|
|
78
|
+
**Cuándo usar:**
|
|
79
|
+
- **Ad-hoc (cambio directo):** Ops simple — cambiar a warden en TUI sin pasar por foreman. Ej: bump version, restart service, audit one-off.
|
|
80
|
+
- **Plan formal:** Warden crea plan con `metadata.category="ops"`, dispatcha via `task_create_batch` con agent="ci-smith"/"deploy-smith"/etc.
|
|
81
|
+
- **Dispatched:** Foreman crea plan code+ops, dispatcha warden para portions ops. Warden hereda `plan_id` via session.
|
|
82
|
+
|
|
83
|
+
**Regla de oro:** Warden NUNCA dispatcha a craftsman/smith/foreman. Si la tarea es code+ops → pedir al usuario que foreman planifique primero.
|
|
84
|
+
|
|
85
|
+
### ranger
|
|
86
|
+
|
|
87
|
+
**Analyst / Cartographer / Onboarding.** Primary agent para análisis profundo, mapeo estructural y context-loading. NO implementa lógica de negocio. NO crea planes — produce filas en tabla `analyses` (linkable a planes via `source_plan_id`).
|
|
88
|
+
|
|
89
|
+
- **Model:** minimax/MiniMax-M3
|
|
90
|
+
- **Temperature:** 0.3
|
|
91
|
+
- **Permissions:** edit (deny código fuente, allow `docs/analyses/**`), write (ask), bash (ask con read-only allow), webfetch (ask), question (allow), task (scout/sage/scribe)
|
|
92
|
+
- **Delegates to:** scout, sage, scribe (exploración y análisis únicamente)
|
|
93
|
+
- **File:** `agents/ranger.md`
|
|
94
|
+
|
|
95
|
+
**Cuándo usar:**
|
|
96
|
+
- **Ad-hoc:** Análisis directo sin plan — onboarding de proyecto, auditoría exploratoria, context-loading rápido. Cambiar a ranger en TUI sin pasar por foreman.
|
|
97
|
+
- **Plan-mode (consumidor):** Foreman/craftsman/warden crea plan con task agent="ranger". Ranger hereda plan_id y produce analysis linkeada a `source_plan_id`.
|
|
98
|
+
- **Dispatched:** Analysis standalone consultable por otros primaries via `analysis_get`/`analysis_search`.
|
|
99
|
+
|
|
100
|
+
**Tres roles híbridos:**
|
|
101
|
+
| Rol | Output típico |
|
|
102
|
+
|---|---|
|
|
103
|
+
| Analyst | Auditoría arquitectura, deuda técnica, security review, performance hotspots — findings con severity + location + recommendation |
|
|
104
|
+
| Cartographer | Dependency graphs, module maps, convention detection, symbol indexes |
|
|
105
|
+
| Onboarding | Stack detection, conventions extraction, entry points, context-load denso para humanos/AI nuevos |
|
|
106
|
+
|
|
107
|
+
**Routing interno (sub-agentes permitidos):**
|
|
108
|
+
| Sub-agente | Uso |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `scout` | Mapear repo, localizar archivos, dependency graphs |
|
|
111
|
+
| `sage` | Evaluar arquitectura, trade-offs, debugging complejo |
|
|
112
|
+
| `scribe` | Research docs externas, APIs, versiones |
|
|
113
|
+
|
|
114
|
+
**NO delega a:** smiths, painter, chronicler, inspector, foreman, craftsman, warden, guild.
|
|
115
|
+
|
|
116
|
+
**Regla de oro:** ranger analiza y mapea. Craftsman implementa. Warden opera. Foreman planifica.
|
|
117
|
+
|
|
118
|
+
## Explorers
|
|
119
|
+
|
|
120
|
+
Read-only agents for investigation and research.
|
|
121
|
+
|
|
122
|
+
### scout
|
|
123
|
+
|
|
124
|
+
Codebase reconnaissance. Navigates repositories at maximum speed: maps structures, locates symbols, finds patterns, traces dependencies.
|
|
125
|
+
|
|
126
|
+
- **Model:** opencode-go/minimax-m2.7
|
|
127
|
+
- **Temperature:** 0.3
|
|
128
|
+
- **Permissions:** edit (deny), bash (allow), question (allow)
|
|
129
|
+
- **File:** `agents/scout.md`
|
|
130
|
+
- **Use when:** "find where X is defined", "map the auth flow", "what calls this function"
|
|
131
|
+
|
|
132
|
+
### scribe
|
|
133
|
+
|
|
134
|
+
External knowledge retrieval. Searches documentation, APIs, libraries, and web resources. Does not modify code.
|
|
135
|
+
|
|
136
|
+
- **Model:** opencode-go/minimax-m2.7
|
|
137
|
+
- **Temperature:** 0.3
|
|
138
|
+
- **Permissions:** edit (deny), bash (allow), question (allow)
|
|
139
|
+
- **File:** `agents/scribe.md`
|
|
140
|
+
- **Use when:** "research best practices for X", "find docs for this library", "what version of Y should we use"
|
|
141
|
+
|
|
142
|
+
## Builders
|
|
143
|
+
|
|
144
|
+
Write code. Divided into generic smith and stack-specific smiths.
|
|
145
|
+
|
|
146
|
+
### painter
|
|
147
|
+
|
|
148
|
+
UI/UX designer. Handles visual composition, component design, and frontend styling. Triggered only when `stack === "vue"` and `type === "design"`.
|
|
149
|
+
|
|
150
|
+
- **Model:** opencode-go/kimi-k2.6
|
|
151
|
+
- **Temperature:** 0.2
|
|
152
|
+
- **Permissions:** edit (allow), write (allow), bash (allow), question (allow)
|
|
153
|
+
- **File:** `agents/painter.md`
|
|
154
|
+
- **Use when:** "design a login page", "create a dashboard layout", "style this component"
|
|
155
|
+
|
|
156
|
+
### smith
|
|
157
|
+
|
|
158
|
+
Generic fast implementation specialist. Stack-agnostic — handles well-defined tasks in any language. Default fallback for unknown stacks.
|
|
159
|
+
|
|
160
|
+
- **Model:** opencode-go/deepseek-v4-flash
|
|
161
|
+
- **Temperature:** 0.1
|
|
162
|
+
- **Permissions:** edit (allow), write (allow), bash (allow), question (allow)
|
|
163
|
+
- **File:** `agents/smith.md`
|
|
164
|
+
- **Use when:** small fixes, config changes, simple features, mechanical refactors in any language
|
|
165
|
+
|
|
166
|
+
### go-smith
|
|
167
|
+
|
|
168
|
+
Go implementation specialist. Idiomatic Go patterns, testing with table-driven tests, concurrency, error handling.
|
|
169
|
+
|
|
170
|
+
- **Model:** xiaomi/mimo-v2.5-pro
|
|
171
|
+
- **Temperature:** 0.1
|
|
172
|
+
- **Permissions:** edit (allow), write (allow), bash (allow)
|
|
173
|
+
- **File:** `agents/go-smith.md`
|
|
174
|
+
|
|
175
|
+
### js-smith
|
|
176
|
+
|
|
177
|
+
JavaScript/TypeScript implementation specialist. Frontend and backend JS/TS, modern patterns, typing.
|
|
178
|
+
|
|
179
|
+
- **Model:** xiaomi/mimo-v2.5-pro
|
|
180
|
+
- **Temperature:** 0.1
|
|
181
|
+
- **Permissions:** edit (allow), write (allow), bash (allow)
|
|
182
|
+
- **File:** `agents/js-smith.md`
|
|
183
|
+
|
|
184
|
+
### python-smith
|
|
185
|
+
|
|
186
|
+
Python implementation specialist. Pythonic idioms, type hints, testing with pytest.
|
|
187
|
+
|
|
188
|
+
- **Model:** xiaomi/mimo-v2.5-pro
|
|
189
|
+
- **Temperature:** 0.1
|
|
190
|
+
- **Permissions:** edit (allow), write (allow), bash (allow)
|
|
191
|
+
- **File:** `agents/python-smith.md`
|
|
192
|
+
|
|
193
|
+
### vue-smith
|
|
194
|
+
|
|
195
|
+
Vue 3 / Pinia implementation specialist. Composition API, `<script setup>`, Vue Router, Pinia stores.
|
|
196
|
+
|
|
197
|
+
- **Model:** xiaomi/mimo-v2.5-pro
|
|
198
|
+
- **Temperature:** 0.1
|
|
199
|
+
- **Permissions:** edit (allow), write (allow), bash (allow)
|
|
200
|
+
- **File:** `agents/vue-smith.md`
|
|
201
|
+
|
|
202
|
+
### zig-smith
|
|
203
|
+
|
|
204
|
+
Zig 0.16 implementation specialist. Systems programming, Zig idioms, memory management.
|
|
205
|
+
|
|
206
|
+
- **Model:** xiaomi/mimo-v2.5-pro
|
|
207
|
+
- **Temperature:** 0.1
|
|
208
|
+
- **Permissions:** edit (allow), write (allow), bash (allow)
|
|
209
|
+
- **File:** `agents/zig-smith.md`
|
|
210
|
+
|
|
211
|
+
### rust-smith
|
|
212
|
+
|
|
213
|
+
Rust implementation specialist. Ownership, lifetimes, traits, async/Tokio, zero-cost abstractions, compile-time SQL checks.
|
|
214
|
+
|
|
215
|
+
- **Model:** opencode-go/mimo-v2.5-pro
|
|
216
|
+
- **Temperature:** 0.1
|
|
217
|
+
- **Permissions:** edit (allow), write (allow), bash (allow)
|
|
218
|
+
- **File:** `agents/rust-smith.md`
|
|
219
|
+
- **Skills:** `rust-patterns`, `rust-testing`
|
|
220
|
+
|
|
221
|
+
## Advisors
|
|
222
|
+
|
|
223
|
+
Provide analysis, guidance, and multi-perspective evaluation. Read-only (edit denied).
|
|
224
|
+
|
|
225
|
+
### sage
|
|
226
|
+
|
|
227
|
+
Architecture advisor and high-risk debugger. Deep reasoning for complex problems, trade-off analysis, architecture decisions.
|
|
228
|
+
|
|
229
|
+
- **Model:** opencode-go/deepseek-v4-pro
|
|
230
|
+
- **Temperature:** 0.2
|
|
231
|
+
- **Permissions:** edit (deny), bash (allow), question (allow)
|
|
232
|
+
- **File:** `agents/sage.md`
|
|
233
|
+
- **Use when:** "review this architecture", "debug this complex issue", "what's the best approach for X"
|
|
234
|
+
- **Manual call:** `sage <question>`
|
|
235
|
+
- **Auto-triggered:** when `type === "debug" && risk === "high"`, or `high risk implement` (as advisory parallel to the stack-smith)
|
|
236
|
+
|
|
237
|
+
### guild
|
|
238
|
+
|
|
239
|
+
Multi-LLM consensus and architectural debate. Simulates multiple perspectives to reach consensus on high-risk decisions.
|
|
240
|
+
|
|
241
|
+
- **Model:** opencode-go/deepseek-v4-pro
|
|
242
|
+
- **Temperature:** 0.3
|
|
243
|
+
- **Permissions:** edit (deny), bash (allow), question (allow)
|
|
244
|
+
- **File:** `agents/guild.md`
|
|
245
|
+
- **Use when:** "should we use X or Y architecture", "is this design safe"
|
|
246
|
+
- **Note:** Manual delegation only — never auto-routed by the scheduler. The foreman only delegates to guild on explicit user request (`type: "debate"`).
|
|
247
|
+
|
|
248
|
+
## Quality
|
|
249
|
+
|
|
250
|
+
Review and documentation agents. Invoked after implementation phases.
|
|
251
|
+
|
|
252
|
+
### inspector
|
|
253
|
+
|
|
254
|
+
Code quality and security auditor. Reviews diffs for bugs, anti-patterns, security issues, and style violations.
|
|
255
|
+
|
|
256
|
+
- **Model:** opencode-go/deepseek-v4-pro
|
|
257
|
+
- **Temperature:** 0.2
|
|
258
|
+
- **Permissions:** edit (deny), bash (allow), question (allow)
|
|
259
|
+
- **File:** `agents/inspector.md`
|
|
260
|
+
- **Use when:** "review this diff", "audit this PR", "check for security issues"
|
|
261
|
+
- **Auto-triggered:** craftsman invokes inspector on the resulting diff before closing any task
|
|
262
|
+
|
|
263
|
+
### chronicler
|
|
264
|
+
|
|
265
|
+
Technical documentation writer. Analyzes code and generates Markdown documentation. Read-only on existing code; writes documentation files.
|
|
266
|
+
|
|
267
|
+
- **Model:** opencode-go/deepseek-v4-flash
|
|
268
|
+
- **Temperature:** 0.2
|
|
269
|
+
- **Permissions:** edit (allow), write (allow), bash (deny)
|
|
270
|
+
- **File:** `agents/chronicler.md`
|
|
271
|
+
- **Use when:** "document this API", "generate README", "write migration guide"
|
|
272
|
+
|
|
273
|
+
## Ops Sub-Agents
|
|
274
|
+
|
|
275
|
+
Warden delega en 4 sub-agentes especializados en operaciones:
|
|
276
|
+
|
|
277
|
+
### ci-smith
|
|
278
|
+
|
|
279
|
+
**CI/CD pipeline specialist.** Crea/modifica GitHub Actions, GitLab CI, CircleCI workflows.
|
|
280
|
+
|
|
281
|
+
- **Model:** opencode-go/deepseek-v4-flash
|
|
282
|
+
- **Temperature:** 0.5
|
|
283
|
+
- **Permissions:** edit (allow for `.github/workflows/*.yml`), bash (allow for `gh workflow*`)
|
|
284
|
+
- **Delegates to:** none (focused specialist)
|
|
285
|
+
- **File:** `agents/ci-smith.md`
|
|
286
|
+
|
|
287
|
+
### deploy-smith
|
|
288
|
+
|
|
289
|
+
**Deployment automation specialist.** Scripts de deploy, Docker, k8s, rollback procedures.
|
|
290
|
+
|
|
291
|
+
- **Model:** opencode-go/deepseek-v4-flash
|
|
292
|
+
- **Temperature:** 0.5
|
|
293
|
+
- **Permissions:** edit (allow for `scripts/deploy*`, `Dockerfile*`, `k8s/`)
|
|
294
|
+
- **Delegates to:** none
|
|
295
|
+
- **File:** `agents/deploy-smith.md`
|
|
296
|
+
|
|
297
|
+
### release-smith
|
|
298
|
+
|
|
299
|
+
**Release management specialist.** Semver, CHANGELOG, GitHub releases, branch strategy enforcement.
|
|
300
|
+
|
|
301
|
+
- **Model:** opencode-go/deepseek-v4-flash
|
|
302
|
+
- **Temperature:** 0.3
|
|
303
|
+
- **Permissions:** edit (allow for `CHANGELOG.md`, `package.json`)
|
|
304
|
+
- **Delegates to:** none
|
|
305
|
+
- **File:** `agents/release-smith.md`
|
|
306
|
+
|
|
307
|
+
### ops-scout
|
|
308
|
+
|
|
309
|
+
**Infrastructure reconnaissance specialist.** Audit de CI/CD/deploy/monitoring/secrets. Read-only.
|
|
310
|
+
|
|
311
|
+
- **Model:** opencode-go/deepseek-v4-flash
|
|
312
|
+
- **Temperature:** 0.5
|
|
313
|
+
- **Permissions:** edit (deny), write (deny), bash (allow read-only)
|
|
314
|
+
- **Delegates to:** none
|
|
315
|
+
- **File:** `agents/ops-scout.md`
|
|
316
|
+
- **Note:** Cross-primary — único sub-agent dispatchable por warden O foreman.
|
|
317
|
+
|
|
318
|
+
## Routing Tables
|
|
319
|
+
|
|
320
|
+
Routing is split across two tiers: foreman (planner) and craftsman (implementer). The old centralized scheduler in `src/orchestrator/scheduler.ts` is deprecated.
|
|
321
|
+
|
|
322
|
+
### Foreman Routing (planning only)
|
|
323
|
+
|
|
324
|
+
Foreman delegates exclusively to exploration/analysis agents:
|
|
325
|
+
|
|
326
|
+
| Petición | Delegar a |
|
|
327
|
+
|----------|-----------|
|
|
328
|
+
| Localizar código / mapear repo / detectar stack | `scout` |
|
|
329
|
+
| Research docs / APIs / libraries / versiones | `scribe` |
|
|
330
|
+
| Arquitectura / debugging difícil / trade-offs | `sage` |
|
|
331
|
+
| Consenso multi-modelo / debate arquitectónico | `guild` (solo manual) |
|
|
332
|
+
|
|
333
|
+
**NO delegar a:** smiths, painter, chronicler, inspector — esos van al craftsman. **ranger es primary peer** (no sub-agente de foreman) — foreman puede crear planes con `task agent="ranger"` para análisis pre-implement, pero NO lo dispatcha como sub-agente de exploración.
|
|
334
|
+
|
|
335
|
+
### Craftsman Routing (implementation)
|
|
336
|
+
|
|
337
|
+
Craftsman selecciona sub-agente por extensión de archivo (`task.files`) con `task.metadata.stack` como override:
|
|
338
|
+
|
|
339
|
+
| Extensión | Sub-agente |
|
|
340
|
+
|-----------|------------|
|
|
341
|
+
| `.go` | `go-smith` |
|
|
342
|
+
| `.vue` / `.svelte` | `vue-smith` |
|
|
343
|
+
| `.ts` / `.tsx` / `.js` / `.jsx` | `js-smith` |
|
|
344
|
+
| `.py` | `python-smith` |
|
|
345
|
+
| `.rs` | `rust-smith` |
|
|
346
|
+
| `.zig` | `zig-smith` |
|
|
347
|
+
| UI/design + `type=design` | `painter` |
|
|
348
|
+
| Documentación / markdown | `chronicler` |
|
|
349
|
+
| Auditoría / diff review | `inspector` |
|
|
350
|
+
| Sin match | `smith` (genérico) |
|
|
351
|
+
|
|
352
|
+
Si no hay match por extensión ni stack, usa `smith` como fallback genérico.
|
|
353
|
+
|
|
354
|
+
### Cross-Primary Routing (ranger)
|
|
355
|
+
|
|
356
|
+
Ranger es primary peer. Craftsman, foreman y warden pueden **invocar** ranger vía:
|
|
357
|
+
|
|
358
|
+
| Mecanismo | Uso |
|
|
359
|
+
|---|---|
|
|
360
|
+
| `analysis_*` tools | Consumir analyses existentes (read) — todos los primaries |
|
|
361
|
+
| `task_create_batch` con `agent: "ranger"` | Dispatchar análisis como task en un plan (foreman/craftsman) |
|
|
362
|
+
| `session_start({planId})` (ranger hereda) | Análisis linkeado a plan via `source_plan_id` |
|
|
363
|
+
|
|
364
|
+
**Cuándo craftsman invoca ranger:**
|
|
365
|
+
- Pre-implement context-loading profundo (antes de refactor multi-archivo)
|
|
366
|
+
- Auditoría de deuda técnica antes de fix
|
|
367
|
+
- Validación de assumptions arquitectónicas
|
|
368
|
+
|
|
369
|
+
**Cuándo warden invoca ranger:**
|
|
370
|
+
- Auditoría de proyecto pre-CI overhaul
|
|
371
|
+
- Security scan profundo pre-deploy
|
|
372
|
+
|
|
373
|
+
## Custom Agents
|
|
374
|
+
|
|
375
|
+
See [configuration.md](configuration.md#custom-agents) for instructions on adding new specialist agents to the routing table.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Bug: Plan orphaned in `draft` — `plan_approve` / `plan_update_status` fail with FK session validation
|
|
2
|
+
|
|
3
|
+
## Resumen
|
|
4
|
+
|
|
5
|
+
Planes creados mediante `plan_create` no pueden transicionar a `approved` ni a ningún estado terminal porque `plan_approve` y `plan_update_status` validan que `ctx.sessionID` exista en la tabla `sessions` (`src/db/plans.ts:118-121`, `src/db/plans.ts:159-162`). Dicho ID nunca se inserta en `sessions` porque corresponde a la sesión del harness (OpenCode), no a un `session_start` explícito. El plan queda bloqueado permanentemente en `draft`.
|
|
6
|
+
|
|
7
|
+
## Entorno
|
|
8
|
+
|
|
9
|
+
| Atributo | Valor |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Sistema | ndomo plugin (OpenCode) |
|
|
12
|
+
| Fecha de detección | 2026-06-19 |
|
|
13
|
+
| Plan afectado | `7e6659fd-1e54-415d-84d5-ca52804145c5` / `test-plan-creation-2026-06-19` |
|
|
14
|
+
| Sesión smoke test | `ses_test_smoke_2026-06-19` |
|
|
15
|
+
| Sesión OpenCode (no insertada) | `ses_11ef21ff8ffeC9CTJhd8nlcgBP` |
|
|
16
|
+
| Estado actual del plan | `draft` (irreversible) |
|
|
17
|
+
|
|
18
|
+
## Reproducción
|
|
19
|
+
|
|
20
|
+
1. Crear plan:
|
|
21
|
+
```
|
|
22
|
+
plan_create({slug: "test-plan-creation-2026-06-19", title: "...", overview: "..."})
|
|
23
|
+
```
|
|
24
|
+
→ Plan creado en status `draft`.
|
|
25
|
+
|
|
26
|
+
2. Crear tareas (opcional, no interfiere):
|
|
27
|
+
```
|
|
28
|
+
task_create_batch({planId: "7e6659fd-...", tasks: [...]})
|
|
29
|
+
```
|
|
30
|
+
→ `task_create_batch` funciona correctamente (no valida sessionId).
|
|
31
|
+
|
|
32
|
+
3. Intentar aprobar:
|
|
33
|
+
```
|
|
34
|
+
plan_approve({id: "7e6659fd-1e54-415d-84d5-ca52804145c5"})
|
|
35
|
+
```
|
|
36
|
+
→ Error:
|
|
37
|
+
```
|
|
38
|
+
ndomo: session_id does not exist: ses_11ef21ff8ffeC9CTJhd8nlcgBP
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
4. Intentar cerrar (alternativa, mismo error):
|
|
42
|
+
```
|
|
43
|
+
plan_update_status({id: "7e6659fd-...", status: "completed"})
|
|
44
|
+
```
|
|
45
|
+
→ Error idéntico:
|
|
46
|
+
```
|
|
47
|
+
ndomo: session_id does not exist: ses_11ef21ff8ffeC9CTJhd8nlcgBP
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Comportamiento esperado vs observado
|
|
51
|
+
|
|
52
|
+
| Operación | Esperado | Observado |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `plan_approve` | Plan transiciona a `approved` con `approved_at` seteado | Error `session_id does not exist`; plan permanece `draft` |
|
|
55
|
+
| `plan_update_status` | Plan transiciona a `completed`/`failed`/`abandoned` y se auto-archiva | Error `session_id does not exist`; plan permanece `draft` |
|
|
56
|
+
| Auto-archive en terminal status | Markdown generado en `<project>/.ndomo/archives/plans/` | Nunca se invoca (bloqueado por error previo) |
|
|
57
|
+
|
|
58
|
+
## Causa raíz
|
|
59
|
+
|
|
60
|
+
El bug es un side-effect del **Fix #1** (validación FK de `session_id` introducida para integridad referencial entre `plans` y `sessions`). Las tools MCP pasan `ctx.sessionID` (ID de sesión interno del harness OpenCode) como `opts.sessionId` a las funciones de transición:
|
|
61
|
+
|
|
62
|
+
- `plan_approve` → `src/plugin.ts:649-651`: llama `approvePlan(db, args.id, {sessionId: ctx.sessionID, ...})`.
|
|
63
|
+
- `plan_update_status` → `src/plugin.ts:672-675`: llama `updatePlanStatus(db, args.id, args.status, {sessionId: ctx.sessionID, ...})`.
|
|
64
|
+
|
|
65
|
+
Ambas funciones en `src/db/plans.ts` ejecutan la validación FK:
|
|
66
|
+
|
|
67
|
+
- `updatePlanStatus` → `src/db/plans.ts:118-121`: `SELECT 1 FROM sessions WHERE id = ?` — si no existe fila, lanza error.
|
|
68
|
+
- `approvePlan` → `src/db/plans.ts:159-162`: idéntica validación.
|
|
69
|
+
|
|
70
|
+
La tabla `sessions` solo recibe filas mediante `session_start` explícito (`src/db/sessions.ts:24-38`). El `ctx.sessionID` del harness OpenCode (`ses_11ef21ff8ffeC9CTJhd8nlcgBP`) **nunca es insertado** en `sessions`. Por tanto, cualquier transición de estado que pase `ctx.sessionID` falla.
|
|
71
|
+
|
|
72
|
+
`plan_create` no se ve afectado porque su implementación no valida `sessionId` contra la tabla (`src/plugin.ts:556-592`). `task_create_batch` tampoco valida — solo pasa `planId`.
|
|
73
|
+
|
|
74
|
+
## Workarounds
|
|
75
|
+
|
|
76
|
+
1. **Ninguno desde user-side**: El harness OpenCode no expone control sobre `ctx.sessionID`, por lo que no es posible invocar `session_start` con un ID que coincida antes de `plan_approve`.
|
|
77
|
+
|
|
78
|
+
2. **SQL directo (acceso a DB)**: Si se tiene acceso al archivo `.ndomo/state.db`, se puede insertar manualmente la fila faltante:
|
|
79
|
+
```sql
|
|
80
|
+
INSERT INTO sessions (id, started_at, last_checkpoint, goal, state, agent_history)
|
|
81
|
+
VALUES ('ses_11ef21ff8ffeC9CTJhd8nlcgBP', 1729300000000, 1729300000000, 'fix orphan plan FK', '{}', '[]');
|
|
82
|
+
```
|
|
83
|
+
Luego `plan_approve` y `plan_update_status` funcionarán. No es aplicable en producción / entorno multi-agente.
|
|
84
|
+
|
|
85
|
+
3. **No recomendado**: Deshabilitar la validación FK localmente editando `src/db/plans.ts`. Rompe la integridad referencial y se pierde en el próximo pull.
|
|
86
|
+
|
|
87
|
+
## Fix propuesto
|
|
88
|
+
|
|
89
|
+
Tres opciones, ordenadas por impacto/costo descendente:
|
|
90
|
+
|
|
91
|
+
### a) Auto-insertar fila en `sessions` al primer `plan_create`
|
|
92
|
+
|
|
93
|
+
Capturar `ctx.sessionID` en `plan_create` e insertar automáticamente un registro en `sessions` si no existe. Zero-touch para el usuario. Garantiza que toda tool posterior que use el mismo `ctx.sessionID` encuentre la fila. Costo: ~5 líneas en `src/plugin.ts:plan_create`.
|
|
94
|
+
|
|
95
|
+
### b) Relajar validación con upsert automático (lazy)
|
|
96
|
+
|
|
97
|
+
En `approvePlan` y `updatePlanStatus`, si `opts.sessionId` no existe en `sessions`, hacer un `INSERT OR IGNORE` automático antes de la transición. Similar a (a) pero diferido al momento de la primera transición. Menos intrusivo, pero permite que se cree la fila sin goal ni metadata.
|
|
98
|
+
|
|
99
|
+
### c) Documentar requisito de `session_start` previo
|
|
100
|
+
|
|
101
|
+
Exigir que toda transición de plan esté precedida por un `session_start` explícito en el mismo flujo. Workaround oficial pero no práctico para el foreman — añade un paso manual frágil.
|
|
102
|
+
|
|
103
|
+
**Opción recomendada**: (a) por simplicidad y cobertura total del lifecycle.
|
|
104
|
+
|
|
105
|
+
## Impacto
|
|
106
|
+
|
|
107
|
+
- Todo plan creado por el foreman (o cualquier agente) en una sesión de OpenCode sin `session_start` previo queda bloqueado en `draft`.
|
|
108
|
+
- La DB acumula planes `draft` no cerrables que nunca reciben auto-archive.
|
|
109
|
+
- El `plan_update_status` con status terminal (`completed`, `failed`, `abandoned`) nunca se ejecuta, por lo que el auto-archive a markdown (`src/plugin.ts:677-688`) no se dispara.
|
|
110
|
+
- El lifecycle de 10 pasos del foreman (`docs/database.md:202-219`) se rompe en el paso 3 (`plan_approve`).
|
|
111
|
+
- No hay pérdida de datos — los planes existen en DB — pero no se pueden gestionar programáticamente.
|
|
112
|
+
|
|
113
|
+
## Referencias
|
|
114
|
+
|
|
115
|
+
- Código tool MCP `plan_approve`: `src/plugin.ts:644-655`
|
|
116
|
+
- Código tool MCP `plan_update_status`: `src/plugin.ts:657-689`
|
|
117
|
+
- Validación FK en `updatePlanStatus`: `src/db/plans.ts:117-121`
|
|
118
|
+
- Validación FK en `approvePlan`: `src/db/plans.ts:158-162`
|
|
119
|
+
- Inserción de sesiones solo vía `session_start`: `src/db/sessions.ts:24-38`
|
|
120
|
+
- Tool MCP `plan_create`: `src/plugin.ts:556-592`
|
|
121
|
+
- Plan afectado: `7e6659fd-1e54-415d-84d5-ca52804145c5` / `test-plan-creation-2026-06-19`
|
|
122
|
+
- Sesión OpenCode no insertada: `ses_11ef21ff8ffeC9CTJhd8nlcgBP`
|
|
123
|
+
- Mensaje original del error: `msg_ee10e0bab001kwzDMkf5gpxY7T`
|
|
124
|
+
|
|
125
|
+
## Status
|
|
126
|
+
|
|
127
|
+
| Campo | Valor |
|
|
128
|
+
|---|---|
|
|
129
|
+
| Estado | `Detected` |
|
|
130
|
+
| Workaround | `none user-side` |
|
|
131
|
+
| Fix | `pending` |
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Bug: `task_create_batch` UNIQUE constraint collision on retries/splits
|
|
2
|
+
|
|
3
|
+
## Resumen
|
|
4
|
+
|
|
5
|
+
`task_create_batch` falla con `UNIQUE constraint failed: plan_tasks.plan_id, plan_tasks.order_index` cuando el caller invoca la tool 2+ veces para el mismo plan (retry post-abort, dispatch cross-step, o re-creación tras abort). El bug también se manifiesta en splits cross-stack cuando los decimales generados colisionan con order_indices existentes.
|
|
6
|
+
|
|
7
|
+
## Entorno
|
|
8
|
+
|
|
9
|
+
| Atributo | Valor |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Sistema | ndomo plugin (OpenCode) |
|
|
12
|
+
| Fecha de detección | 2026-06-22 |
|
|
13
|
+
| Plan de evidencia | `18252705-9c4e-4f5b-85a8-4f7153ceb101` |
|
|
14
|
+
| Plan de fix | `ca69222a-808a-41b0-9dae-05f7641be308` |
|
|
15
|
+
| Sesión de fix | `ses_craftsman_ca69222a` |
|
|
16
|
+
| Archivos afectados | `src/db/tasks.ts`, `tools/task_create_batch.ts`, `src/plugin.ts` |
|
|
17
|
+
|
|
18
|
+
## Reproducción
|
|
19
|
+
|
|
20
|
+
1. Crear plan y poblar con 1 task (1ra invocación de `task_create_batch`):
|
|
21
|
+
```
|
|
22
|
+
plan_create({slug: "feat-x", title: "...", overview: "..."})
|
|
23
|
+
task_create_batch({planId: "...", tasks: [{description: "task 0", agent: "craftsman"}]})
|
|
24
|
+
```
|
|
25
|
+
→ Task creada en `order_index=0`. OK.
|
|
26
|
+
|
|
27
|
+
2. Invocar `task_create_batch` nuevamente para el mismo plan (2da invocación):
|
|
28
|
+
```
|
|
29
|
+
task_create_batch({planId: "...", tasks: [
|
|
30
|
+
{description: "task A", agent: "craftsman"},
|
|
31
|
+
{description: "task B", agent: "js-smith"},
|
|
32
|
+
...
|
|
33
|
+
]})
|
|
34
|
+
```
|
|
35
|
+
→ Error:
|
|
36
|
+
```
|
|
37
|
+
UNIQUE constraint failed: plan_tasks.plan_id, plan_tasks.order_index
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. El bug también ocurre en splits cross-stack: si un task con files multi-stack genera sub-tasks con `orderIndex = parentOrder + 0.1 * stackIdx`, los decimales pueden colisionar con order_indices existentes.
|
|
41
|
+
|
|
42
|
+
## Comportamiento esperado vs observado
|
|
43
|
+
|
|
44
|
+
| Operación | Esperado | Observado |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| 2da invocación de `task_create_batch` | Nuevas tasks asignadas a order_index secuenciales sin colisión | `UNIQUE constraint failed` — transacción abortada |
|
|
47
|
+
| Split cross-stack con decimales ocupados | Sub-tasks reasignadas a slots libres | `UNIQUE constraint failed` si decimal colisiona |
|
|
48
|
+
| Caller pasa `orderIndex` explícito que colisiona | Core reasigna a slot libre | `UNIQUE constraint failed` |
|
|
49
|
+
|
|
50
|
+
## Causa raíz
|
|
51
|
+
|
|
52
|
+
Doble causa: callers + core confiaban en `orderIndex` del caller sin validación contra DB existente.
|
|
53
|
+
|
|
54
|
+
### Caller: `orderIndex: idx` forzado
|
|
55
|
+
|
|
56
|
+
Los callers en `tools/task_create_batch.ts:47` y `src/plugin.ts:965` pasaban `orderIndex: idx` desde `array.map((t, idx) => ...)`:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// tools/task_create_batch.ts (ANTES del fix)
|
|
60
|
+
args.tasks.map((t, idx) => ({
|
|
61
|
+
orderIndex: idx, // ← solo único intra-batch, no considera tasks existentes
|
|
62
|
+
...
|
|
63
|
+
}))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`idx` es 0, 1, 2... dentro del batch, pero no considera tasks ya persistidas en el plan. En la 2da invocación, `idx=0` colisiona con la task existente en `order_index=0`.
|
|
67
|
+
|
|
68
|
+
### Core: `createTasksBatch` confiaba en caller's `orderIndex`
|
|
69
|
+
|
|
70
|
+
`src/db/tasks.ts:createTasksBatch` usaba `effectiveTask.orderIndex ?? i` como autoridad directa para el INSERT, sin validar contra `plan_tasks` existentes:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// src/db/tasks.ts (ANTES del fix)
|
|
74
|
+
const orderIndex = effectiveTask.orderIndex ?? i;
|
|
75
|
+
db.query(`INSERT INTO plan_tasks ... VALUES (...)`).run(id, planId, orderIndex, ...);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Sin pre-loop `SELECT MAX(order_index)`, sin set de order_indices usados, sin retry on UNIQUE violation.
|
|
79
|
+
|
|
80
|
+
### Split cross-stack: decimales sin validación
|
|
81
|
+
|
|
82
|
+
El split M7 generaba `orderIndex = (t.orderIndex ?? results.length) + stackIdx * 0.1` sin verificar si los decimales ya existían en la DB.
|
|
83
|
+
|
|
84
|
+
## Workaround histórico
|
|
85
|
+
|
|
86
|
+
Mientras el fix no estaba aplicado, el foreman usó INSERT directo via `bun:sqlite` con `order_index` explícito pre-calculado desde `MAX+1`:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const db = new Database('.ndomo/state.db');
|
|
90
|
+
const maxRow = db.query("SELECT MAX(order_index) as m FROM plan_tasks WHERE plan_id = ? AND archived_at IS NULL").get(planId);
|
|
91
|
+
let order = (maxRow?.m ?? -1) + 1;
|
|
92
|
+
// for each task: INSERT with order_index=order++ en transacción
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
4 tasks del plan `ca69222a` fueron creadas via este workaround. Sus metadatos fueron actualizados con `{bugWorkaroundApplied: true, bugSlug: "task_create_batch-order-index-collision", originalInsertMethod: "bun-sqlite3-direct", fixedIn: "ca69222a-..."}`.
|
|
96
|
+
|
|
97
|
+
## Fix aplicado
|
|
98
|
+
|
|
99
|
+
**Estrategia**: defense-in-depth en `createTasksBatch` — el core es la autoridad para `order_index`, no el caller.
|
|
100
|
+
|
|
101
|
+
### Cambios en `src/db/tasks.ts:createTasksBatch`
|
|
102
|
+
|
|
103
|
+
1. **Pre-loop `SELECT MAX(order_index)`**: calcula `nextFreeInteger` desde el MAX de tasks no-archived.
|
|
104
|
+
2. **`usedOrderIndices` set**: recolecta TODOS los order_indices existentes (incluyendo archived) para collision detection. El `UNIQUE(plan_id, order_index)` no filtra por `archived_at`.
|
|
105
|
+
3. **`allocateOrderIndex(preferred)` helper**: intenta el slot preferido del caller; si está ocupado o undefined, cae a `nextFreeInteger` e incrementa hasta encontrar slot libre.
|
|
106
|
+
4. **`allocateSplitOrderIndex(parentOrder, stackIdx)` helper**: `stackIdx=0` → parentOrder (ya alocado); `stackIdx>0` → `parentOrder + stackIdx * 0.1`; si decimal ocupado → escala a siguiente integer libre.
|
|
107
|
+
5. **Try/catch SQLITE_CONSTRAINT UNIQUE con retry**: defense-in-depth — si una race condition o edge case produce colisión, reasigna y reintenta (hasta 10 intentos).
|
|
108
|
+
6. **Signature change**: `orderIndex` ahora es optional en el input type (`Omit<PlanTask, ... | "orderIndex"> & { orderIndex?: number }`).
|
|
109
|
+
|
|
110
|
+
### Cambios en callers
|
|
111
|
+
|
|
112
|
+
- `tools/task_create_batch.ts`: eliminado `orderIndex: idx` del `.map()`.
|
|
113
|
+
- `src/plugin.ts`: eliminado `orderIndex: idx` del `.map()`.
|
|
114
|
+
|
|
115
|
+
Los callers ahora pasan `orderIndex: undefined` (implícito), y el core aloca dinámicamente.
|
|
116
|
+
|
|
117
|
+
## Tests de regresión
|
|
118
|
+
|
|
119
|
+
7 tests añadidos a `src/db/tasks.test.ts`:
|
|
120
|
+
|
|
121
|
+
| Test | Escenario |
|
|
122
|
+
|---|---|
|
|
123
|
+
| (a) | Plan pre-poblado (task en 0) + nueva task con `orderIndex=0` → reasigna a 1 |
|
|
124
|
+
| (b) | Split cross-stack con plan pre-poblado → parent=1, decimal=1.1 |
|
|
125
|
+
| (c) | Split cross-stack con decimales ocupados (0.1, 0.2) → escala a integers 1, 2 |
|
|
126
|
+
| (d) | Caller pasa `orderIndex` colisionante → core reasigna |
|
|
127
|
+
| (e) | Reproduce bug exacto: 1st batch OK, 2nd batch 4 tasks con `orderIndex` colisionante → todas reasignadas |
|
|
128
|
+
| (f) | Caller omite `orderIndex` → core asigna secuencial desde MAX+1 |
|
|
129
|
+
| (g) | Archived task ocupa slot → nueva task evita colisión |
|
|
130
|
+
|
|
131
|
+
## Lección replicable
|
|
132
|
+
|
|
133
|
+
> Any tool que mapea `array → unique-constraint-key` es unsafe para re-invocación sobre mismo parent. Always: lookup current max/sequence BEFORE generating keys, never trust caller-provided sequential indices.
|
|
134
|
+
|
|
135
|
+
El patrón `array.map((item, idx) => ({ orderIndex: idx, ... }))` es seguro solo intra-batch. Para safety cross-batch, el core debe:
|
|
136
|
+
1. Query DB state antes de generar keys
|
|
137
|
+
2. Tratar caller-provided keys como hints, no como autoridad
|
|
138
|
+
3. Retry on constraint violation con reasignación
|
|
139
|
+
|
|
140
|
+
## Referencias
|
|
141
|
+
|
|
142
|
+
- Schema constraint: `src/db/schema.ts:53` — `UNIQUE(plan_id, order_index)`
|
|
143
|
+
- Core fix: `src/db/tasks.ts:createTasksBatch` — pre-loop MAX + allocateOrderIndex + allocateSplitOrderIndex + try/catch retry
|
|
144
|
+
- Caller fix (tools): `tools/task_create_batch.ts:44` — eliminado `orderIndex: idx`
|
|
145
|
+
- Caller fix (plugin): `src/plugin.ts:962` — eliminado `orderIndex: idx`
|
|
146
|
+
- Tests: `src/db/tasks.test.ts` — describe "order_index collision-safe allocation"
|
|
147
|
+
- Plan de fix: `ca69222a-808a-41b0-9dae-05f7641be308`
|
|
148
|
+
- Plan de evidencia: `18252705-9c4e-4f5b-85a8-4f7153ceb101`
|
|
149
|
+
|
|
150
|
+
## Status
|
|
151
|
+
|
|
152
|
+
| Campo | Valor |
|
|
153
|
+
|---|---|
|
|
154
|
+
| Estado | `Resolved` |
|
|
155
|
+
| Workaround | `bun:sqlite direct INSERT con order_index manual` (obsoleto post-fix) |
|
|
156
|
+
| Fix | `ca69222a-808a-41b0-9dae-05f7641be308` — applied 2026-06-22 |
|
|
157
|
+
| Tests | 7 regression tests — 49/49 pass (42 existing + 7 new) |
|
|
158
|
+
| Suite | 356/356 pass (full project) |
|