@ancleto/spec 0.6.34 → 0.6.36

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 CHANGED
@@ -222,6 +222,10 @@ Se guarda en `.ancletorc` → `discovery.exclude` y se preserva en `update`/`upg
222
222
 
223
223
  La excepción: si el agente detecta un **hueco concreto** (info que el seed debería tener y no tiene, como una paleta nueva sin unidad), puede ofrecer regenerar aunque el `impact` sea `minor` — siempre con tu aprobación. Nunca regenera solo.
224
224
 
225
+ ### Regeneración parcial
226
+
227
+ `--check` también devuelve `affectedDocs`: los documentos del seed cuyas áreas cambiaron. Cuando aprobás una regeneración sobre un reporte `minor`, el seed-writer **reescribe solo esos documentos** y deja el resto intacto (un solo pack igual, pero sin reescribir los 8 documentos + dossiers). El mapeo vive en `seed-map.json` dentro del directorio del seed y lo mantiene la skill al generar; sin ese archivo el chequeo degrada a lista vacía y la regeneración es completa.
228
+
225
229
  ---
226
230
 
227
231
  ## Uso rápido
@@ -71,7 +71,7 @@ The orchestrator never executes `ancleto discovery --check`. When the technical
71
71
 
72
72
  For `MISSING` and `PARTIAL`, never generate automatically. Ask for explicit user approval before invoking `@technical-seed-writer`. If approved, pass the complete check report returned by `@technical-discovery` and the action `generate` or `complete`. If declined, continue only when the request can be handled safely without broad repository context; otherwise report the limitation and stop.
73
73
 
74
- For `STALE`, delegate `regenerate` to `@technical-seed-writer` only after explicit user approval. When `impact` is `minor`, keep working from the seed and mention the drift in the summary. A concrete gap found while working — needed info the seed does not cover — may justify offering regeneration even with `minor` impact, always with approval.
74
+ For `STALE`, delegate `regenerate` to `@technical-seed-writer` only after explicit user approval. When `impact` is `minor`, keep working from the seed and mention the drift in the summary. A concrete gap found while working — needed info the seed does not cover — may justify offering regeneration even with `minor` impact, always with approval. When the report lists `affectedDocs`, pass them so an approved regeneration stays scoped to those documents.
75
75
 
76
76
  `@technical-discovery` owns read-only repository orientation and seed-state inspection. `@technical-seed-writer` owns all seed generation, completion, and regeneration. Never substitute an unavailable seed with a manual repository scan.
77
77
 
@@ -127,7 +127,7 @@ When you were delegated, do not address the developer as if they had called you:
127
127
  - **Answer in Spanish.** Paths, file names, and identifiers stay as they are.
128
128
  - Cite the paths that support each claim.
129
129
  - State which source you used and whether seed inspection was required.
130
- - When seed inspection was required, return the resolved state with its `impact`, and the complete unchanged check report if a seed action is needed.
130
+ - When seed inspection was required, return the resolved state with its `impact` and `affectedDocs`, and the complete unchanged check report if a seed action is needed.
131
131
  - Declare — when they apply — the stale nodes, any direct code read, and any gap left by the budget.
132
132
  - If the seed state prevented the answer, state that no repository sweep was performed and
133
133
  include the required `SEED_ACTION_REQUIRED` marker.
@@ -45,11 +45,11 @@ action was explicitly requested, run `ancleto discovery --check` once and read i
45
45
  1. For `READY`, do not rewrite the seed; report that it is already available.
46
46
  2. For `MISSING`, `PARTIAL`, or an accepted `STALE` regeneration, execute the skill's single global Repomix command exactly once. It must use the resolved `config.exclude` values and
47
47
  must not run `--include`, an uncompressed preliminary pack, or another compressed pack.
48
- 3. Read that pack and immediately write every required seed document in Spanish under
49
- `config.outputDir`, including `units/_map.md`. Use the same pack for any optional dossier.
50
- 4. Run `ancleto discovery --check` once to validate the written seed. This is state-only: do
48
+ 3. Read that pack and write the seed documents under `config.outputDir`, including `units/_map.md`. When the passed report has `impact: minor` with a non-empty `affectedDocs` list, rewrite ONLY those documents and leave every other seed document untouched. Otherwise (full regeneration), write every required seed document. Use the same pack for any optional dossier.
49
+ 4. Write `seed-map.json` in `config.outputDir` mapping every seed document to the source areas it draws evidence from. Areas are first-level directories (or root file names), the same convention the CLI uses. Example: `{ "version": 1, "generatedAt": "...", "docs": { "overview.md": ["src", "package.json"] } }`.
50
+ 5. Run `ancleto discovery --check` once to validate the written seed. This is state-only: do
51
51
  not generate another pack after it.
52
- 5. Return the result as specified in `## Output`.
52
+ 6. Return the result as specified in `## Output`.
53
53
 
54
54
  If the pack fails, writing fails, or the final state is not usable, report the exact blocker;
55
55
  do not claim the seed was generated.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ancleto/spec",
3
- "version": "0.6.34",
3
+ "version": "0.6.36",
4
4
  "description": "Orquestador SDD liviano con subagentes optimizados para costo/tokens",
5
5
  "type": "module",
6
6
  "bin": {
@@ -51,6 +51,11 @@ do not split it or create child documents.
51
51
 
52
52
  Run `ancleto discovery --check` once more and apply `validation-checklist.md`. This is a
53
53
  state-only validation; it must not be followed by another Repomix command. For stale nodes,
54
- regenerate only the affected documents using one new global generation on a later request.
54
+ regenerate only the affected documents using one new global generation on a later request:
55
+ when the check report lists `affectedDocs`, those are the only documents to rewrite.
55
56
  If a unit has no dossier, update its row in `units/_map.md` and only the root documents whose
56
57
  content changed.
58
+
59
+ After any generation, write `seed-map.json` in `config.outputDir` mapping every seed document
60
+ to the source areas (first-level directories or root file names) it draws evidence from, so a
61
+ later `--check` can scope the next regeneration.
@@ -9,4 +9,5 @@
9
9
  - [ ] `decisions.md` concentrates rules, risks, and coupling without duplicating the overview.
10
10
  - [ ] `inventory.md` declares directories or globs, not file lists.
11
11
  - [ ] `unknowns.md` records what could not be verified.
12
+ - [ ] `seed-map.json` exists and maps each seed document to its source areas.
12
13
  - [ ] No secret values are present.
package/src/cli/index.js CHANGED
@@ -1059,8 +1059,24 @@ async function initProject(args) {
1059
1059
  await writeFile(tierFile, tier + '\n')
1060
1060
  }
1061
1061
 
1062
- const manifest = await writeManifest(projectDir, { azure, discovery, agent, language })
1062
+ // Paridad con install: el tier elegido tambien reescribe los modelos de los
1063
+ // agentes locales. Antes quedaba huerfano (tier declarado, modelos de otro tier).
1064
+ let gratisModelChoice = null
1065
+ if (tier === 'gratis') {
1066
+ const persisted = isKnownGratisModel(existing?.gratisModel) ? existing.gratisModel : null
1067
+ gratisModelChoice = persisted || gratisModel()
1068
+ }
1069
+ const manifest = await writeManifest(projectDir, {
1070
+ azure,
1071
+ discovery,
1072
+ agent,
1073
+ language,
1074
+ ...(gratisModelChoice ? { gratisModel: gratisModelChoice } : {})
1075
+ })
1063
1076
  await copyTemplates(projectDir)
1077
+ if (tier) {
1078
+ await applyTier(join(projectDir, '.opencode', 'agents'), tier, tierModels(tier, TIERS, gratisModelChoice))
1079
+ }
1064
1080
  await scaffoldAspec(projectDir)
1065
1081
  await refreshWorkingContext(projectDir)
1066
1082
  await registerProject(projectDir, { agent, tier: tier || null })
@@ -1207,6 +1223,28 @@ function computeImpact(stored, sources, fileHashes) {
1207
1223
  return { impact, changedAreas: [...changedAreas].sort(), materialReasons, notes }
1208
1224
  }
1209
1225
 
1226
+ // seed-map.json lo escribe la skill al generar: cada documento → areas de
1227
+ // primer nivel (o archivos raiz) de las que saco evidencia. Misma convencion que areaOf.
1228
+ async function readSeedMap(outputDir) {
1229
+ try {
1230
+ const raw = await readFile(join(outputDir, 'seed-map.json'), 'utf8')
1231
+ const parsed = JSON.parse(raw.replace(/^\uFEFF/, ''))
1232
+ if (!parsed || typeof parsed.docs !== 'object' || parsed.docs === null) return null
1233
+ return parsed.docs
1234
+ } catch {
1235
+ return null
1236
+ }
1237
+ }
1238
+
1239
+ function affectedDocsFor(seedMap, changedAreas) {
1240
+ if (!seedMap || !changedAreas.length) return []
1241
+ const out = new Set()
1242
+ for (const [doc, areas] of Object.entries(seedMap)) {
1243
+ if (Array.isArray(areas) && areas.some((a) => changedAreas.includes(a))) out.add(doc)
1244
+ }
1245
+ return [...out].sort()
1246
+ }
1247
+
1210
1248
  function statePath(outputDir) {
1211
1249
  return join(outputDir, '.discovery-state.json')
1212
1250
  }
@@ -1229,7 +1267,7 @@ async function checkDiscovery() {
1229
1267
  const { outputDir } = config
1230
1268
  const tier = readProjectTier(process.cwd())
1231
1269
  const present = await presentDocs(outputDir)
1232
- let state, action, message, missingDocs, impact = 'none', changedAreas = [], materialReasons = [], notes = []
1270
+ let state, action, message, missingDocs, impact = 'none', changedAreas = [], materialReasons = [], notes = [], affectedDocs = [], seedMapPresent = false
1233
1271
  if (present.length === 0) {
1234
1272
  state = 'MISSING'
1235
1273
  action = 'generate'
@@ -1265,6 +1303,9 @@ async function checkDiscovery() {
1265
1303
  changedAreas = diff.changedAreas
1266
1304
  materialReasons = diff.materialReasons
1267
1305
  notes = diff.notes
1306
+ const seedMap = await readSeedMap(outputDir)
1307
+ seedMapPresent = seedMap !== null
1308
+ affectedDocs = affectedDocsFor(seedMap, changedAreas)
1268
1309
  state = 'STALE'
1269
1310
  if (impact === 'material') {
1270
1311
  action = 'regenerate'
@@ -1286,6 +1327,8 @@ async function checkDiscovery() {
1286
1327
  changedAreas,
1287
1328
  materialReasons,
1288
1329
  notes,
1330
+ affectedDocs,
1331
+ seedMapPresent,
1289
1332
  config: {
1290
1333
  outputDir,
1291
1334
  exclude: config.exclude,