@ancleto/spec 0.6.25 → 0.6.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/agents/documenter.md +7 -6
- package/agents/orchestrator.md +1 -1
- package/agents/spec-writer.md +3 -2
- package/package.json +1 -1
- package/skills/ancleto-archive/SKILL.md +19 -4
- package/skills/ancleto-verify/SKILL.md +6 -0
- package/src/cli/index.js +78 -1
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
- [Características principales](#características-principales)
|
|
24
24
|
- [Requisitos](#requisitos)
|
|
25
25
|
- [Instalación](#instalación)
|
|
26
|
+
- [Guía de .gitignore](#guía-de-gitignore)
|
|
26
27
|
- [Configuración y tiers de costo](#configuración-y-tiers-de-costo)
|
|
27
28
|
- [Uso rápido](#uso-rápido)
|
|
28
29
|
- [Referencia de comandos CLI](#referencia-de-comandos-cli)
|
|
@@ -121,6 +122,20 @@ Cómo bajarlo:
|
|
|
121
122
|
|
|
122
123
|
---
|
|
123
124
|
|
|
125
|
+
## Guía de .gitignore
|
|
126
|
+
|
|
127
|
+
El framework **no gestiona tu `.gitignore`**: es una decisión del proyecto. Como referencia, esta es la combinación que funciona bien con Ancleto:
|
|
128
|
+
|
|
129
|
+
| Ruta | Recomendación | Por qué |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `aspec/` | **Versionar** | Es la fuente-de-verdad: specs y el historial de changes archivados. Si se ignora, el equipo pierde el por qué de las decisiones y los agentes no tienen contexto estable. |
|
|
132
|
+
| `.ancleto/` | **Ignorar** | Base SQLite local (memoria del repo y working-context). Es de tu máquina; cada integrante la regenera. |
|
|
133
|
+
| `.ancletorc` | **Decisión del equipo** | Versionarlo alinea la configuración del proyecto (agente, tier, discovery). Ignoralo si cada integrante lo personaliza. |
|
|
134
|
+
|
|
135
|
+
> Error común: gitignorear `aspec/` junto con `.ancleto/`. La memoria local es descartable; la fuente-de-verdad, no.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
124
139
|
## Configuración y tiers de costo
|
|
125
140
|
|
|
126
141
|
En la primera ejecución, un **wizard interactivo** te guía por los pasos de configuración (agente/IDE y nivel de gasto). También podés pasarlos por flags:
|
|
@@ -186,6 +201,9 @@ ancleto upgrade # Re-aplica templates y skills respetando tus personal
|
|
|
186
201
|
|
|
187
202
|
ancleto check # Verifica la integridad de la instalación (faltantes / huérfanos)
|
|
188
203
|
|
|
204
|
+
ancleto specs check [--change <nombre>] [--json]
|
|
205
|
+
# Valida keywords canónicos en aspec/specs y en los deltas de un change
|
|
206
|
+
|
|
189
207
|
ancleto doctor # Diagnostica el entorno (Node, node:sqlite, opencode.json)
|
|
190
208
|
|
|
191
209
|
ancleto memory context [--scope <project|feature|task>] [--out <archivo>]
|
package/agents/documenter.md
CHANGED
|
@@ -105,12 +105,13 @@ Do not treat archive as a blind file move.
|
|
|
105
105
|
- `tasks.md`
|
|
106
106
|
- `specs/` when the change includes delta specs
|
|
107
107
|
3. Determine whether the change includes spec deltas that must be reflected in `aspec/specs/`
|
|
108
|
-
4.
|
|
109
|
-
5. If
|
|
110
|
-
6. If the change is ready
|
|
111
|
-
7.
|
|
112
|
-
8.
|
|
113
|
-
9.
|
|
108
|
+
4. When deltas exist, run `ancleto specs check --change {change-name} --json` before merging. If a delta spec or its target source-of-truth spec is non-canonical (translated keywords), stop and report it instead of merging into it
|
|
109
|
+
5. If needed, update the relevant source-of-truth specs so they reflect the completed change
|
|
110
|
+
6. If the change is not ready for archive, stop and report the inconsistency clearly
|
|
111
|
+
7. If the change is ready, archive it to `aspec/changes/archive/{YYYY-MM-DD}-{change-name}/`
|
|
112
|
+
8. Remove the original active change directory from `aspec/changes/{change-name}/` after a successful archive
|
|
113
|
+
9. If any files or folders remain in the original active change directory, report them as cleanup issues instead of leaving silent residue
|
|
114
|
+
10. Return a short structured summary of the finalization and archive result
|
|
114
115
|
|
|
115
116
|
## Source-of-Truth Rules
|
|
116
117
|
|
package/agents/orchestrator.md
CHANGED
|
@@ -259,7 +259,7 @@ If the runtime is read-only or plan-only:
|
|
|
259
259
|
5. **Testing**: Delegate to **`@tester`** to create or update unit tests and perform final validation against the approved scope, including a non-writing format check and one lint pass after all edits. Include the complete Resolved Context Envelope, the coder's task-owned files, and its reported risks. `@tester` returns the final union of task-owned files and the Validation Ledger. A format failure in a task-owned file is failed verification and must not be fixed silently by `@tester`.
|
|
260
260
|
6. **Review**: Delegate to **`@reviewer`** when an independent correctness or scope review is appropriate. Include the complete Resolved Context Envelope, the tester's final task-owned file union, and the full Validation Ledger.
|
|
261
261
|
7. **🛑 FINAL ARCHIVE CHECKPOINT**: If implementation, validation, and any required review are complete with no blocking issues, stop and explicitly ask the user whether to keep iterating on the same change or finalize and archive it
|
|
262
|
-
8. **Finalization / Archive**: Before archiving, verify the delta specs reflect the final implementation — if iteration changed behavior or scope, re-delegate to **`@spec-writer`** to update the delta specs first, so the merge to source-of-truth is accurate. Then delegate to **`@documenter`** only after explicit user approval to archive
|
|
262
|
+
8. **Finalization / Archive**: Before archiving, verify the delta specs reflect the final implementation — if iteration changed behavior or scope, re-delegate to **`@spec-writer`** to update the delta specs first, so the merge to source-of-truth is accurate. Check the seed freshness before closing: delegate the read-only state check to **`@technical-discovery`** and, when the state is `STALE`, warn and offer regeneration via **`@technical-seed-writer`** with explicit user approval — never automatic; if the user declines, continue the archive normally. This closure check protects future sessions from a silently aging seed. Then delegate to **`@documenter`** only after explicit user approval to archive
|
|
263
263
|
9. **Memory Record**: After a successful archive, delegate to **`@memory-keeper`** in Automatic Record mode when a concrete, plausible candidate exists, with the factual completed-work summary and validation evidence supporting it. If no candidate exists, delegate Optional Draft with the factual completed-work summary, `no-automatic-candidate` classification, and the reason no candidate was identified. When Automatic Record returns `no-entry-warranted`, use its draft or delegate Optional Draft with that classification and reason if it could not provide one. Before asking, show the final work summary, classification, reason, and exact draft, then stop for explicit user approval. If no safe draft is available, report that outcome and finish without asking or storing. On approval, delegate User-Approved Record with the unchanged draft, the factual summary and validation evidence used to compose it, archived change name, and card context. On rejection, do not store.
|
|
264
264
|
10. **Report**: Return final status or blocking findings to the user after automatic storage, an explicit decline, or User-Approved Record completes.
|
|
265
265
|
|
package/agents/spec-writer.md
CHANGED
|
@@ -44,8 +44,9 @@ using the `ancleto-workflow` skill and the conventions of:
|
|
|
44
44
|
- `design.md`
|
|
45
45
|
- `tasks.md`
|
|
46
46
|
- `specs/{capability}/spec.md` as needed
|
|
47
|
-
6.
|
|
48
|
-
7.
|
|
47
|
+
6. Before returning, verify every generated spec uses the canonical keywords (`### Requirement:`, `#### Scenario:`, `**WHEN**`/`**THEN**`, `SHALL`); fix any translated heading or keyword in place — never leave a spec with translated keywords.
|
|
48
|
+
7. When the delegation says the context was produced without a technical seed, declare that in the generated artifacts.
|
|
49
|
+
8. Return a brief summary with generated paths, scope, and open questions or risks
|
|
49
50
|
|
|
50
51
|
## Change Naming
|
|
51
52
|
|
package/package.json
CHANGED
|
@@ -31,6 +31,8 @@ Read `aspec/changes/<name>/tasks.md` (when present); count `- [ ]` (incomplete)
|
|
|
31
31
|
|
|
32
32
|
Check for delta specs at `aspec/changes/<name>/specs/`; if none, proceed without a sync prompt.
|
|
33
33
|
|
|
34
|
+
**Canonical keywords first:** when delta specs exist and the CLI is available, run `ancleto specs check --change <name> --json`. If a delta spec or its target source-of-truth spec is non-canonical (translated keywords), warn that requirement blocks cannot be matched reliably and ask whether to normalize the affected spec first or archive without syncing that capability. Never merge into a non-canonical spec silently.
|
|
35
|
+
|
|
34
36
|
**If delta specs exist:**
|
|
35
37
|
|
|
36
38
|
- Diff each delta against its main spec at `aspec/specs/<capability>/spec.md`; show a combined summary of adds/modifications/removals/renames.
|
|
@@ -42,14 +44,24 @@ Check for delta specs at `aspec/changes/<name>/specs/`; if none, proceed without
|
|
|
42
44
|
- `## RENAMED Requirements` → rename using the `FROM:`/`TO:` pair.
|
|
43
45
|
- Proceed to archive regardless of the choice.
|
|
44
46
|
|
|
45
|
-
### 3.
|
|
47
|
+
### 3. Check technical seed freshness
|
|
48
|
+
|
|
49
|
+
If the `ancleto` CLI is available in this project, run `ancleto discovery --check` (read-only) and read its JSON `state`.
|
|
50
|
+
|
|
51
|
+
- **`STALE`**: warn that the technical seed is out of date for the current code, and offer to regenerate it now (follow the `ancleto-technical-discovery` skill) or to continue archiving with the stale seed.
|
|
52
|
+
- **Any other state**: continue without comment.
|
|
53
|
+
- Never regenerate automatically — regenerate only with the user's explicit approval.
|
|
54
|
+
- If the user declines, continue the archive normally and record the stale seed in the summary.
|
|
55
|
+
- If the CLI is unavailable, skip this step.
|
|
56
|
+
|
|
57
|
+
### 4. Perform the archive
|
|
46
58
|
|
|
47
59
|
Create `aspec/changes/archive/` if absent. Target: `YYYY-MM-DD-<change-name>` (current date).
|
|
48
60
|
|
|
49
61
|
- **Target exists**: fail, suggesting a rename or different date.
|
|
50
62
|
- **Otherwise**: delete scaffold-only files (e.g., `aspec/changes/<name>/context.md` — never preserved), then move the change directory to `aspec/changes/archive/YYYY-MM-DD-<name>/`.
|
|
51
63
|
|
|
52
|
-
###
|
|
64
|
+
### 5. Capture lessons into persistent memory
|
|
53
65
|
|
|
54
66
|
Record what this change taught:
|
|
55
67
|
|
|
@@ -64,7 +76,7 @@ Record what this change taught:
|
|
|
64
76
|
|
|
65
77
|
Record only durable lessons — decisions whose reasons aren't visible in the result, constraints, verified workarounds. Never workflow meta. If nothing meets the bar, record nothing and say so.
|
|
66
78
|
|
|
67
|
-
###
|
|
79
|
+
### 6. Display summary
|
|
68
80
|
|
|
69
81
|
```
|
|
70
82
|
## Archive Complete
|
|
@@ -75,12 +87,15 @@ Record only durable lessons — decisions whose reasons aren't visible in the re
|
|
|
75
87
|
**Memories recorded:** <list, or "None">
|
|
76
88
|
```
|
|
77
89
|
|
|
78
|
-
|
|
90
|
+
When the seed was `STALE`, add a `**Seed:**` line: `Regenerated` or `Stale — regeneration declined`.
|
|
91
|
+
|
|
92
|
+
If warnings applied (incomplete tasks, skipped sync, stale seed), show an "Archive Complete (with warnings)" variant listing them.
|
|
79
93
|
|
|
80
94
|
## Guardrails
|
|
81
95
|
|
|
82
96
|
- Always prompt for change selection if not provided.
|
|
83
97
|
- Don't block archiving on warnings — inform and confirm.
|
|
98
|
+
- Never regenerate the technical seed automatically: offer it, and continue the archive if the user declines.
|
|
84
99
|
- If the target archive directory exists, fail instead of overwriting.
|
|
85
100
|
- Delete scaffold-only files (`context.md`) before moving; never archive them.
|
|
86
101
|
- `recordRule`/`recordDecision` accept only `memory_key`, `content`, `justification`, `scope`. Never send `source`, `confidence`, `status` or `id` — the runtime manages those.
|
|
@@ -26,6 +26,12 @@ Read under `aspec/changes/<name>/`: `tasks.md` (required), plus `proposal.md`, `
|
|
|
26
26
|
|
|
27
27
|
**Tasks:** parse checkboxes (`- [ ]` incomplete vs `- [x]` complete) and count. Each incomplete → CRITICAL: "Complete task: `<description>`" or "Mark as done if already implemented".
|
|
28
28
|
|
|
29
|
+
**Canonical spec keywords:** run `ancleto specs check --change <name> --json` when the CLI is available. For every reported file:
|
|
30
|
+
- Delta spec (`aspec/changes/<name>/specs/...`) → CRITICAL: "Non-canonical spec: `<file>` (missing: `<keywords>`)" + "Normalize `<file>` to the canonical English keywords before archiving".
|
|
31
|
+
- Source-of-truth spec (`aspec/specs/...`) → WARNING with the same recommendation.
|
|
32
|
+
|
|
33
|
+
A non-canonical spec cannot be matched for coverage or merged reliably, so report it before the checks below.
|
|
34
|
+
|
|
29
35
|
**Spec coverage:** for each `### Requirement:` in `specs/`, search the codebase for related keywords. Unimplemented → CRITICAL: "Requirement not found: `<requirement name>`" + "Implement requirement: `<description>`".
|
|
30
36
|
|
|
31
37
|
### 3. Correctness
|
package/src/cli/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { cp, mkdir, access, writeFile, readFile, readdir, rename } from 'node:fs
|
|
|
4
4
|
import { spawnSync } from 'node:child_process'
|
|
5
5
|
import { createHash } from 'node:crypto'
|
|
6
6
|
import { createInterface } from 'node:readline'
|
|
7
|
-
import { join, dirname, resolve, basename } from 'node:path'
|
|
7
|
+
import { join, dirname, resolve, basename, relative } from 'node:path'
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
9
|
import { homedir, tmpdir } from 'node:os'
|
|
10
10
|
import { createMemoryEngine, createReadonlyMemoryEngine, defaultMemoryDbPath } from '../core/memory/engine.js'
|
|
@@ -40,6 +40,9 @@ Uso:
|
|
|
40
40
|
ancleto discovery --check Estado del seed (READY/STALE/PARTIAL/MISSING)
|
|
41
41
|
ancleto discovery [--compress] [--include G] [--ignore G] [--token-budget N]
|
|
42
42
|
Empaca el repo con Repomix y guarda estado
|
|
43
|
+
ancleto specs check [--change <name>] [--json]
|
|
44
|
+
Valida keywords canonicos (### Requirement:, WHEN, THEN)
|
|
45
|
+
en aspec/specs y, con --change, en los deltas del change
|
|
43
46
|
ancleto memory context [--scope X] [--out file]
|
|
44
47
|
Imprime/escribe el bloque <ProjectMemoryRules> (reglas activas)
|
|
45
48
|
ancleto memory list [--type X] [--scope X] [--all] [--json]
|
|
@@ -980,6 +983,77 @@ async function checkTierOrphan(cwd) {
|
|
|
980
983
|
return warnings
|
|
981
984
|
}
|
|
982
985
|
|
|
986
|
+
// Un spec es canonico cuando usa los keywords literales en ingles. Detecta specs
|
|
987
|
+
// traducidos (p. ej. "#### RF1:" + "**Dado**/**Cuando**/**Entonces**"), que rompen
|
|
988
|
+
// la cobertura por keywords y el merge de deltas.
|
|
989
|
+
function specIssues(content) {
|
|
990
|
+
const missing = []
|
|
991
|
+
if (!/^###\s+Requirement:/m.test(content)) missing.push('### Requirement:')
|
|
992
|
+
if (/^####\s+Scenario:/m.test(content)) {
|
|
993
|
+
if (!/\bWHEN\b/.test(content)) missing.push('WHEN')
|
|
994
|
+
if (!/\bTHEN\b/.test(content)) missing.push('THEN')
|
|
995
|
+
}
|
|
996
|
+
const unexpected = []
|
|
997
|
+
const re = /^####\s+(.+)$/gm
|
|
998
|
+
let m
|
|
999
|
+
while ((m = re.exec(content)) !== null) {
|
|
1000
|
+
if (!/^Scenario\b/.test(m[1])) unexpected.push(`#### ${m[1]}`)
|
|
1001
|
+
}
|
|
1002
|
+
return { missing, unexpected }
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
async function collectSpecFiles(dir) {
|
|
1006
|
+
const out = []
|
|
1007
|
+
if (!(await exists(dir))) return out
|
|
1008
|
+
for (const e of await readdir(dir, { withFileTypes: true })) {
|
|
1009
|
+
const p = join(dir, e.name)
|
|
1010
|
+
if (e.isDirectory()) out.push(...(await collectSpecFiles(p)))
|
|
1011
|
+
else if (e.name === 'spec.md') out.push(p)
|
|
1012
|
+
}
|
|
1013
|
+
return out
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
async function specsCheckCommand(args) {
|
|
1017
|
+
const cwd = process.cwd()
|
|
1018
|
+
const asJson = args.includes('--json')
|
|
1019
|
+
const ci = args.indexOf('--change')
|
|
1020
|
+
const change = ci >= 0 ? args[ci + 1] : null
|
|
1021
|
+
const files = await collectSpecFiles(join(cwd, CHANGES_ROOT, 'specs'))
|
|
1022
|
+
if (change) {
|
|
1023
|
+
const changeDir = join(cwd, CHANGES_ROOT, 'changes', change)
|
|
1024
|
+
if (!(await exists(changeDir))) {
|
|
1025
|
+
console.error(`ancleto: no existe el change "${change}" en ${CHANGES_ROOT}/changes/`)
|
|
1026
|
+
process.exit(1)
|
|
1027
|
+
}
|
|
1028
|
+
files.push(...(await collectSpecFiles(join(changeDir, 'specs'))))
|
|
1029
|
+
}
|
|
1030
|
+
const findings = []
|
|
1031
|
+
for (const f of files) {
|
|
1032
|
+
const { missing, unexpected } = specIssues(await readFile(f, 'utf8'))
|
|
1033
|
+
if (missing.length || unexpected.length) {
|
|
1034
|
+
findings.push({ file: relative(cwd, f).replace(/\\/g, '/'), missing, unexpected })
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const ok = findings.length === 0
|
|
1038
|
+
if (asJson) {
|
|
1039
|
+
console.log(JSON.stringify({ ok, scanned: files.length, nonCanonical: findings }, null, 2))
|
|
1040
|
+
} else if (files.length === 0) {
|
|
1041
|
+
console.log('ancleto: no hay specs para revisar')
|
|
1042
|
+
} else {
|
|
1043
|
+
for (const f of files) {
|
|
1044
|
+
const rel = relative(cwd, f).replace(/\\/g, '/')
|
|
1045
|
+
const found = findings.find((x) => x.file === rel)
|
|
1046
|
+
if (found) {
|
|
1047
|
+
console.log(` ✖ ${rel} (revisar: ${[...found.missing, ...found.unexpected].join(', ')})`)
|
|
1048
|
+
} else {
|
|
1049
|
+
console.log(` ✔ ${rel}`)
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
console.log(ok ? `ancleto: ${files.length} spec(s) canonicos` : `ancleto: ${findings.length} de ${files.length} spec(s) sin keywords canonicos`)
|
|
1053
|
+
}
|
|
1054
|
+
process.exit(ok ? 0 : 1)
|
|
1055
|
+
}
|
|
1056
|
+
|
|
983
1057
|
async function doctorCommand() {
|
|
984
1058
|
let fatal = false
|
|
985
1059
|
|
|
@@ -1045,6 +1119,9 @@ switch (cmd) {
|
|
|
1045
1119
|
case 'check':
|
|
1046
1120
|
await checkCommand()
|
|
1047
1121
|
break
|
|
1122
|
+
case 'specs':
|
|
1123
|
+
await specsCheckCommand(rest)
|
|
1124
|
+
break
|
|
1048
1125
|
case 'doctor':
|
|
1049
1126
|
await doctorCommand()
|
|
1050
1127
|
break
|