@ancleto/spec 0.6.26 → 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 +3 -0
- package/agents/documenter.md +7 -6
- package/agents/spec-writer.md +3 -2
- package/package.json +1 -1
- package/skills/ancleto-archive/SKILL.md +2 -0
- package/skills/ancleto-verify/SKILL.md +6 -0
- package/src/cli/index.js +78 -1
package/README.md
CHANGED
|
@@ -201,6 +201,9 @@ ancleto upgrade # Re-aplica templates y skills respetando tus personal
|
|
|
201
201
|
|
|
202
202
|
ancleto check # Verifica la integridad de la instalación (faltantes / huérfanos)
|
|
203
203
|
|
|
204
|
+
ancleto specs check [--change <nombre>] [--json]
|
|
205
|
+
# Valida keywords canónicos en aspec/specs y en los deltas de un change
|
|
206
|
+
|
|
204
207
|
ancleto doctor # Diagnostica el entorno (Node, node:sqlite, opencode.json)
|
|
205
208
|
|
|
206
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/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.
|
|
@@ -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
|