synergyspec-selfevolving 1.1.10 → 1.1.12
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 +12 -3
- package/dist/commands/learn.js +78 -11
- package/dist/commands/self-evolution.d.ts +13 -0
- package/dist/commands/self-evolution.js +156 -20
- package/dist/commands/workflow/status.js +13 -0
- package/dist/core/change-readiness.d.ts +24 -0
- package/dist/core/change-readiness.js +47 -0
- package/dist/core/config-prompts.js +10 -0
- package/dist/core/fitness/health/local-source.d.ts +9 -6
- package/dist/core/fitness/health/local-source.js +9 -6
- package/dist/core/fitness/health/resolve-source.d.ts +4 -3
- package/dist/core/fitness/health/resolve-source.js +5 -4
- package/dist/core/fitness/sample.d.ts +17 -0
- package/dist/core/learn.d.ts +7 -0
- package/dist/core/learn.js +57 -5
- package/dist/core/project-config.d.ts +1 -0
- package/dist/core/project-config.js +11 -8
- package/dist/core/self-evolution/health-baseline.d.ts +24 -0
- package/dist/core/self-evolution/health-baseline.js +78 -0
- package/dist/core/self-evolution/index.d.ts +1 -0
- package/dist/core/self-evolution/index.js +1 -0
- package/dist/core/self-evolution/learn-observation-adapter.d.ts +16 -1
- package/dist/core/self-evolution/learn-observation-adapter.js +101 -15
- package/dist/core/self-evolution/promote.d.ts +25 -0
- package/dist/core/self-evolution/promote.js +21 -0
- package/dist/core/self-evolution/target-evolution.d.ts +7 -0
- package/dist/core/self-evolution/target-evolution.js +9 -0
- package/dist/core/templates/workflows/learn.js +10 -5
- package/package.json +2 -1
- package/scripts/code-health.py +1154 -0
|
@@ -22,7 +22,7 @@ import * as path from 'node:path';
|
|
|
22
22
|
import { relativePath } from './shared.js';
|
|
23
23
|
import { validateLearnEvolutionHint, } from './learn-hints.js';
|
|
24
24
|
import { findCanonicalTargetsByFile, listCanonicalTargets, lookupCanonicalTarget, } from './canonical-targets.js';
|
|
25
|
-
import { isCanonicalTargetEvolvable, } from './target-evolution.js';
|
|
25
|
+
import { isCanonicalTargetEvolvable, explicitTargetIds, } from './target-evolution.js';
|
|
26
26
|
import { detectRepoMode, resolveTargetLocalFiles } from './local-targets.js';
|
|
27
27
|
import { getSchemaDir } from '../artifact-graph/resolver.js';
|
|
28
28
|
import { limitText, } from '../learn.js';
|
|
@@ -288,14 +288,27 @@ export function generateEvolutionHints(report, policy) {
|
|
|
288
288
|
* - A hint that already names a target (`affectedTargetId`) is kept iff that
|
|
289
289
|
* target is evolvable.
|
|
290
290
|
* - A kind-only hint is kept iff at least one registered target of its kind is
|
|
291
|
-
* evolvable
|
|
292
|
-
* `
|
|
293
|
-
*
|
|
294
|
-
*
|
|
291
|
+
* evolvable, and is PINNED to a concrete target (its `affectedTargetId` and
|
|
292
|
+
* `thresholdKey` are filled in) so the downstream propose/gate path treats it
|
|
293
|
+
* as a concrete, single-target hint instead of an `unspecified` group. The pin
|
|
294
|
+
* target is, in order:
|
|
295
|
+
* 1. the single registered target of the hint's kind named explicitly on the
|
|
296
|
+
* CLI via `--evolve-target` (authoritative operator intent — issue #4),
|
|
297
|
+
* 2. otherwise the sole evolvable target of the kind (count heuristic).
|
|
298
|
+
* When neither uniquely resolves (none named, and 0 or >=2 evolvable) the hint
|
|
299
|
+
* stays kind-only/`unspecified`.
|
|
300
|
+
*
|
|
301
|
+
* The explicit-CLI pin (1) exists because `--evolve-target` previously only fed
|
|
302
|
+
* the evolvability POLICY: naming `artifact-template:tasks` left every other
|
|
303
|
+
* artifact-template target evolvable too, so the count heuristic saw >1 and never
|
|
304
|
+
* pinned — the operator's explicit choice was silently dropped and the persisted
|
|
305
|
+
* hint could not be promoted by `evolve-from-edits` (issue #4).
|
|
295
306
|
*
|
|
296
307
|
* When `policy` is undefined the drafts are returned unchanged (back-compat).
|
|
297
308
|
* The HARD oracle/gate freeze is unaffected — oracle files are not canonical
|
|
298
|
-
* targets, so no policy value can name them
|
|
309
|
+
* targets, so no policy value can name them; a CLI-named id is pinned only when
|
|
310
|
+
* it is a registered, same-kind target that is evolvable under the resolved
|
|
311
|
+
* policy (so `--freeze-target` still wins).
|
|
299
312
|
*/
|
|
300
313
|
function scopeHintsByPolicy(drafts, policy) {
|
|
301
314
|
if (!policy)
|
|
@@ -307,24 +320,97 @@ function scopeHintsByPolicy(drafts, policy) {
|
|
|
307
320
|
kept.push(draft);
|
|
308
321
|
continue;
|
|
309
322
|
}
|
|
310
|
-
const
|
|
311
|
-
if (
|
|
312
|
-
continue;
|
|
313
|
-
if (
|
|
314
|
-
const id = evolvable[0].id;
|
|
323
|
+
const pinId = resolveKindOnlyPinTarget(draft, policy);
|
|
324
|
+
if (pinId === null)
|
|
325
|
+
continue; // kind has no evolvable target → drop
|
|
326
|
+
if (pinId !== undefined) {
|
|
315
327
|
kept.push({
|
|
316
328
|
...draft,
|
|
317
|
-
affectedTargetId:
|
|
318
|
-
// `
|
|
329
|
+
affectedTargetId: pinId,
|
|
330
|
+
// `pinId` is already a full `<kind>:<name>` target id, so the grouping key
|
|
319
331
|
// is `<id>:<changeType>` (no doubled kind prefix).
|
|
320
|
-
thresholdKey: `${
|
|
332
|
+
thresholdKey: `${pinId}:${draft.proposedChangeType}`,
|
|
321
333
|
});
|
|
322
334
|
continue;
|
|
323
335
|
}
|
|
324
|
-
kept.push(draft);
|
|
336
|
+
kept.push(draft); // ambiguous → keep kind-only/unspecified
|
|
325
337
|
}
|
|
326
338
|
return kept;
|
|
327
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Decide the concrete target a kind-only hint should be pinned to under the
|
|
342
|
+
* policy. Returns the target id to pin to, `undefined` to keep the hint
|
|
343
|
+
* kind-only (ambiguous — caller leaves it `unspecified`), or `null` to drop the
|
|
344
|
+
* hint entirely (no evolvable target of its kind exists).
|
|
345
|
+
*/
|
|
346
|
+
function resolveKindOnlyPinTarget(draft, policy) {
|
|
347
|
+
// (1) Authoritative operator intent: a single registered, same-kind target
|
|
348
|
+
// named on the CLI via `--evolve-target` pins the hint even when config leaves
|
|
349
|
+
// other same-kind targets evolvable. `isCanonicalTargetEvolvable` honors
|
|
350
|
+
// freeze-wins, so a `--freeze-target`'d id is never pinned.
|
|
351
|
+
const namedOfKind = [...new Set(explicitTargetIds(policy.source.cliEvolve))].filter((id) => lookupCanonicalTarget(id)?.kind === draft.affectedTargetKind &&
|
|
352
|
+
isCanonicalTargetEvolvable(id, policy));
|
|
353
|
+
if (namedOfKind.length === 1)
|
|
354
|
+
return namedOfKind[0];
|
|
355
|
+
// (2) Count heuristic: pin only when exactly one target of the kind is
|
|
356
|
+
// evolvable; drop when none are; otherwise keep kind-only.
|
|
357
|
+
const evolvable = listCanonicalTargets({ kind: draft.affectedTargetKind }).filter((target) => isCanonicalTargetEvolvable(target.id, policy));
|
|
358
|
+
if (evolvable.length === 0)
|
|
359
|
+
return null;
|
|
360
|
+
if (evolvable.length === 1)
|
|
361
|
+
return evolvable[0].id;
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Surface an UNBINDABLE kind-only evolution hint as an actionable DEFECT
|
|
366
|
+
* observation. After {@link scopeHintsByPolicy} runs, a hint that still has no
|
|
367
|
+
* `affectedTargetId` is one that could not be pinned to a concrete target (>1
|
|
368
|
+
* same-kind target evolvable and none named via `--evolve-target`) — it would
|
|
369
|
+
* surface as the `<kind>:unspecified` placeholder and yield a "0 surviving hint
|
|
370
|
+
* group" refusal that is a BINDING DEFECT, not a safe gate refusal. Emitting this
|
|
371
|
+
* is what lets the agent (and the skill) tell the two apart instead of recording a
|
|
372
|
+
* binding bug as "the gate correctly refused".
|
|
373
|
+
*
|
|
374
|
+
* Reads the SCOPED hints directly (no second pin pass), so it cannot drift from
|
|
375
|
+
* {@link scopeHintsByPolicy}. Returns `[]` when `policy` is undefined or nothing is
|
|
376
|
+
* unbindable, keeping learn output byte-identical in the common case.
|
|
377
|
+
*/
|
|
378
|
+
export function detectUnbindableHintObservations(hints, policy) {
|
|
379
|
+
if (!policy)
|
|
380
|
+
return [];
|
|
381
|
+
const byKind = new Map();
|
|
382
|
+
for (const hint of hints) {
|
|
383
|
+
if (hint.affectedTargetId)
|
|
384
|
+
continue; // pinned to a concrete target → fine
|
|
385
|
+
const list = byKind.get(hint.affectedTargetKind) ?? [];
|
|
386
|
+
list.push(hint);
|
|
387
|
+
byKind.set(hint.affectedTargetKind, list);
|
|
388
|
+
}
|
|
389
|
+
const observations = [];
|
|
390
|
+
for (const [kind, kindHints] of byKind) {
|
|
391
|
+
const candidates = listCanonicalTargets({ kind })
|
|
392
|
+
.filter((target) => isCanonicalTargetEvolvable(target.id, policy))
|
|
393
|
+
.map((target) => target.id);
|
|
394
|
+
const evidence = [];
|
|
395
|
+
for (const hint of kindHints) {
|
|
396
|
+
for (const item of hint.evidence) {
|
|
397
|
+
if (evidence.length >= 4)
|
|
398
|
+
break;
|
|
399
|
+
evidence.push({ file: item.file, detail: `unbindable ${kind} hint ${hint.id}` });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
observations.push({
|
|
403
|
+
code: 'evolution-target-unresolved',
|
|
404
|
+
severity: 'defect',
|
|
405
|
+
summary: limitText(`Evolution target unresolved — a kind-only ${kind} hint could not be pinned to a concrete target` +
|
|
406
|
+
(candidates.length > 0 ? ` (candidates: ${candidates.join(', ')})` : '') +
|
|
407
|
+
'. Pass --evolve-target <concrete> to bind it; this is a binding defect, NOT a safe gate refusal.', 300),
|
|
408
|
+
evidence,
|
|
409
|
+
tags: ['evolution', 'unbindable', 'action-required'],
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
return observations;
|
|
413
|
+
}
|
|
328
414
|
function inferTemplateObservation(candidate) {
|
|
329
415
|
const tags = candidate.tags;
|
|
330
416
|
const templateTag = tags.find((tag) => /^template:/i.test(tag));
|
|
@@ -68,7 +68,32 @@ export interface AutoPromoteInput {
|
|
|
68
68
|
baselineLoss: number | null;
|
|
69
69
|
/** When true, require a MEASURED improvement (history < baseline) to promote. */
|
|
70
70
|
requireProvenImprovement: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* This change's RAW measured code-health penalty in [0,1] (the "post" side of
|
|
73
|
+
* the default-path health gate). `null`/omitted ⇒ no health signal ⇒ the
|
|
74
|
+
* health gate does not fire (it can never block on a missing measurement).
|
|
75
|
+
*/
|
|
76
|
+
healthPenalty?: number | null;
|
|
77
|
+
/**
|
|
78
|
+
* Recorded baseline code-health penalty to compare against (the "pre" side;
|
|
79
|
+
* see {@link import('./health-baseline.js').HealthBaseline}). `null`/omitted
|
|
80
|
+
* ⇒ no baseline yet ⇒ the health gate does not fire (first measured run is
|
|
81
|
+
* recorded, not gated).
|
|
82
|
+
*/
|
|
83
|
+
baselineHealthPenalty?: number | null;
|
|
84
|
+
/**
|
|
85
|
+
* How much the health penalty may worsen vs the baseline before it counts as a
|
|
86
|
+
* regression. Defaults to {@link DEFAULT_HEALTH_REGRESSION_MARGIN}; absorbs
|
|
87
|
+
* measurement noise so a trivial uptick does not block the loop.
|
|
88
|
+
*/
|
|
89
|
+
healthRegressionMargin?: number;
|
|
71
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Default slack on the health-regression gate: a change may worsen the measured
|
|
93
|
+
* health penalty by up to this much vs the recorded baseline without being
|
|
94
|
+
* treated as a regression. Keeps measurement noise from blocking promotion.
|
|
95
|
+
*/
|
|
96
|
+
export declare const DEFAULT_HEALTH_REGRESSION_MARGIN = 0.05;
|
|
72
97
|
export interface AutoPromoteDecision {
|
|
73
98
|
promote: boolean;
|
|
74
99
|
reason: string;
|
|
@@ -198,6 +198,12 @@ export async function rollbackCandidatePromotion(layout, candidateId, opts) {
|
|
|
198
198
|
});
|
|
199
199
|
return { candidateId, status: rolled.status, restoredFiles };
|
|
200
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Default slack on the health-regression gate: a change may worsen the measured
|
|
203
|
+
* health penalty by up to this much vs the recorded baseline without being
|
|
204
|
+
* treated as a regression. Keeps measurement noise from blocking promotion.
|
|
205
|
+
*/
|
|
206
|
+
export const DEFAULT_HEALTH_REGRESSION_MARGIN = 0.05;
|
|
201
207
|
/**
|
|
202
208
|
* Pure auto-promote predicate for one-button auto-evolve. The static gate +
|
|
203
209
|
* per-target switch are hard prerequisites; the fitness comparison is the
|
|
@@ -215,6 +221,21 @@ export function shouldAutoPromote(input) {
|
|
|
215
221
|
if (!input.targetEvolvable) {
|
|
216
222
|
return { promote: false, reason: 'target frozen by per-target evolution switch' };
|
|
217
223
|
}
|
|
224
|
+
// Code-health regression gate (default-on health). Independent of the
|
|
225
|
+
// functional/loss history, so it fires even on the no-replay default path
|
|
226
|
+
// where `meanLoss` is null: if THIS change's measured code-health is worse
|
|
227
|
+
// than the recorded baseline by more than the margin, block — do not bake a
|
|
228
|
+
// lesson learned from a health-regressing codebase into the canonical
|
|
229
|
+
// template. No signal (penalty null) or no baseline ⇒ the gate cannot fire.
|
|
230
|
+
if (input.healthPenalty != null && input.baselineHealthPenalty != null) {
|
|
231
|
+
const margin = input.healthRegressionMargin ?? DEFAULT_HEALTH_REGRESSION_MARGIN;
|
|
232
|
+
if (input.healthPenalty > input.baselineHealthPenalty + margin) {
|
|
233
|
+
return {
|
|
234
|
+
promote: false,
|
|
235
|
+
reason: `code-health regression: penalty ${fmt(input.healthPenalty)} > baseline ${fmt(input.baselineHealthPenalty)} + margin ${fmt(margin)}`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
218
239
|
const hasHistory = input.accumulatedCount > 0 && input.meanLoss !== null;
|
|
219
240
|
if (input.requireProvenImprovement) {
|
|
220
241
|
if (!hasHistory || input.baselineLoss === null) {
|
|
@@ -31,6 +31,13 @@ export interface TargetEvolutionPolicy {
|
|
|
31
31
|
cliFreeze?: string;
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* The concrete (non-`all`/`none`) target ids named in a `--evolve-target` /
|
|
36
|
+
* `--freeze-target` csv flag. Public so the learn → hint adapter can treat an
|
|
37
|
+
* explicitly named id as an authoritative pin (the policy map alone loses the
|
|
38
|
+
* distinction between "named on the CLI" and "evolvable via config default").
|
|
39
|
+
*/
|
|
40
|
+
export declare function explicitTargetIds(csv?: string): string[];
|
|
34
41
|
/**
|
|
35
42
|
* Build the effective policy from config + CLI flags. Pure; no I/O.
|
|
36
43
|
*/
|
|
@@ -11,6 +11,15 @@ function parseIds(csv) {
|
|
|
11
11
|
none: tokens.includes('none'),
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* The concrete (non-`all`/`none`) target ids named in a `--evolve-target` /
|
|
16
|
+
* `--freeze-target` csv flag. Public so the learn → hint adapter can treat an
|
|
17
|
+
* explicitly named id as an authoritative pin (the policy map alone loses the
|
|
18
|
+
* distinction between "named on the CLI" and "evolvable via config default").
|
|
19
|
+
*/
|
|
20
|
+
export function explicitTargetIds(csv) {
|
|
21
|
+
return parseIds(csv).ids;
|
|
22
|
+
}
|
|
14
23
|
/**
|
|
15
24
|
* Build the effective policy from config + CLI flags. Pure; no I/O.
|
|
16
25
|
*/
|
|
@@ -9,7 +9,10 @@ Preview-only is the bare-CLI default and the explicit opt-out: run \`synergyspec
|
|
|
9
9
|
**Default Mode: Autonomous self-evolution**
|
|
10
10
|
|
|
11
11
|
- After reviewing the change, you DO the evolution. Do not stop at a report, and do not ask permission.
|
|
12
|
-
- Safety is AUTOMATED, not human-gated: a canonical file is promoted ONLY when the change's test evidence is OBSERVED green (the CLI verifies the ACTUAL test run from the session trajectory, not just the authored \`test-report.md\`), the static gate passes, the target is evolvable under the per-target switch, and a rollback snapshot is taken.
|
|
12
|
+
- Safety is AUTOMATED, not human-gated: a canonical file is promoted ONLY when the change's test evidence is OBSERVED green (the CLI verifies the ACTUAL test run from the session trajectory, not just the authored \`test-report.md\`), the static gate passes, the target is evolvable under the per-target switch, and a rollback snapshot is taken.
|
|
13
|
+
- When nothing canonical is written, CLASSIFY why before moving on — do not blanket-archive every no-op as "safety working":
|
|
14
|
+
- **(a) SAFE refusal (expected, not a bug):** evidence is missing or red, the target is frozen, or the static gate failed on real grounds. The floor refused to evolve on unverified / failing / out-of-scope edits. State the reason in the Evolution Result and move on.
|
|
15
|
+
- **(b) DEFECT (a tool bug to SURFACE, not archive over):** the evolution target could not be RESOLVED or BOUND (an \`evolution-target-unresolved\` observation in the learn output, or a preview target with \`targetId: null\` / \`needsDisambiguation: true\` that still will not bind after you name one concrete \`--evolve-target\`), or promotion failed for a reason that is NOT about evidence / freezing / scope. Nothing was written because the CLI COULD NOT act — not because it correctly declined. Do NOT record this as "safety working": surface it as an unresolved issue (keep an \`incident\` memory entry), name the target id that would not bind, and flag it for a fix. \`synergyspec-selfevolving status\` prints the machine-written \`Evolution:\` outcome — do not contradict it in free text.
|
|
13
16
|
- Frozen gate-defining / oracle files (the gen-test/run-test oracle, schema contracts you were not asked to evolve) are NEVER touched — the CLI rejects any such edit.
|
|
14
17
|
|
|
15
18
|
This run also produces neutral \`observations\` in the JSON output (reflection signals). During autonomous evolution learn persists derived evolution hints to \`.synergyspec-selfevolving/learn-handoffs/<change>/<timestamp>/hints.json\`; you then author the edit and promote it via the \`self-evolution evolve-from-edits\` command in the evolve step. The \`--agent\` flag (a headless \`claude -p\` proposer) is a cron/CI fallback ONLY — never use it when you are the running agent, and never assume \`claude\` exists on a non-Claude host.
|
|
@@ -110,7 +113,7 @@ This run also produces neutral \`observations\` in the JSON output (reflection s
|
|
|
110
113
|
|
|
111
114
|
Unless \`--preview\` was requested, apply the learn writes directly — do not ask which to apply:
|
|
112
115
|
- write the learn report (\`synergyspec-selfevolving/changes/<name>/learn-report.md\`);
|
|
113
|
-
-
|
|
116
|
+
- the approved keep memory entries are written FOR you by this skill's \`learn --apply --yes\` run — it stamps them with the learn-candidate tags + \`synergyspec-selfevolving-learn\` provenance. Do NOT also hand-write them with a bare \`synergyspec-selfevolving memory add\` (that loses the provenance/tag set and double-writes); reserve \`memory add\` for ad-hoc notes that are deliberately NOT learn candidates (report-only / reject candidates stay out of memory either way);
|
|
114
117
|
- persist evolution hints: \`synergyspec-selfevolving learn "<name>" --persist-hints\` (this writes the hints.json you use in the evolve step).
|
|
115
118
|
|
|
116
119
|
\`--preview\` is the only mode that skips these writes.
|
|
@@ -119,7 +122,7 @@ This run also produces neutral \`observations\` in the JSON output (reflection s
|
|
|
119
122
|
|
|
120
123
|
If applying a report, write \`synergyspec-selfevolving/changes/<name>/learn-report.md\` with the preview content plus an "Applied Writes" section.
|
|
121
124
|
|
|
122
|
-
If applying memory entries, use \`synergyspec-selfevolving memory add\` with:
|
|
125
|
+
If applying memory entries MANUALLY (only when you did NOT run \`learn --apply\`, which already writes them with these tags), use \`synergyspec-selfevolving memory add\` with:
|
|
123
126
|
- \`--type workflow\` for reusable workflow lessons
|
|
124
127
|
- \`--type incident\` for problems to avoid
|
|
125
128
|
- \`--tag synspec-learn\`
|
|
@@ -133,9 +136,11 @@ This run also produces neutral \`observations\` in the JSON output (reflection s
|
|
|
133
136
|
|
|
134
137
|
This is the close-the-loop step: you author a concrete improvement to a canonical prompt/template and promote it onto the LOCAL installed file — no rebuild, no republish, no confirmation, no \`claude -p\`.
|
|
135
138
|
|
|
136
|
-
a. **Pick the concrete target + its local file.** From the "Skill/Template Optimization Preview" take the canonical target id and its resolved LOCAL file path (e.g. \`artifact-template:design\` → \`synergyspec-selfevolving/schemas/spec-driven/templates/design.md\`). If the preview
|
|
139
|
+
a. **Pick the concrete target + its local file.** From the "Skill/Template Optimization Preview" take the canonical target id and its resolved LOCAL file path (e.g. \`artifact-template:design\` → \`synergyspec-selfevolving/schemas/spec-driven/templates/design.md\`). If the preview marks a target unbindable / needs-disambiguation (\`targetId: null\`, \`needsDisambiguation: true\`, formerly shown as \`:unspecified\`), choose ONE concrete id from its \`candidateTargetIds\` and pass it explicitly with \`--evolve-target\` (e.g. \`--evolve-target artifact-template:design\`); then re-run the preview to confirm it now binds. If it STILL will not bind after you name a single concrete \`--evolve-target\`, that is the case-(b) DEFECT above (\`evolution-target-unresolved\`) — surface it and stop; do NOT hand-edit the file to work around it.
|
|
137
140
|
|
|
138
|
-
b. **Author the edit yourself.** Reason about the exact prompt/template gap that caused the missed evidence (e.g. the design step missed a stdlib/API-shape compatibility check), then READ
|
|
141
|
+
b. **Author the edit yourself.** Reason about the exact prompt/template gap that caused the missed evidence (e.g. the design step missed a stdlib/API-shape compatibility check), then READ the LOCAL file the preview's \`localFiles\` resolves to and write its FULL improved contents. Keep the change minimal and targeted; never touch frozen oracle files.
|
|
142
|
+
|
|
143
|
+
Author against the path the preview gives you in \`localFiles\` (project-local). For an artifact-template / schema target on the FIRST evolution that project-local base may not exist on disk yet — the preview resolves the path read-only, and \`evolve-from-edits\` MATERIALIZES the canonical default into it (project-local override → user override → packaged default) when you promote. So if reading \`localFiles\` returns "not found", author your full new file against the canonical default content (the same base the CLI will materialize), not against a global copy. Do NOT go hunting in the GLOBAL npm install for the base (e.g. \`npm root -g\` → \`…/AppData/Roaming/npm/node_modules/synergyspec-selfevolving/schemas/…\`), and never edit anything under the global install — the materialize + promote writes land project-local under the repo (the promote write is guarded by an explicit within-repo assertion). If \`localFiles\` is empty, that target has no user-editable local surface here; treat it as the case-(b) DEFECT, not a reason to reach outside the repo.
|
|
139
144
|
|
|
140
145
|
c. **Promote it in one non-interactive command** (validates → gates → observed-verified → promotes onto the local file):
|
|
141
146
|
\`\`\`bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synergyspec-selfevolving",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.12",
|
|
4
4
|
"description": "AI-native system for spec-driven development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"synergyspec-selfevolving",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"schemas",
|
|
38
38
|
"scripts/postinstall.js",
|
|
39
39
|
"scripts/nl2repo_synergyspec-selfevolving_wrapper.py",
|
|
40
|
+
"scripts/code-health.py",
|
|
40
41
|
"!dist/**/*.test.js",
|
|
41
42
|
"!dist/**/__tests__",
|
|
42
43
|
"!dist/**/*.map"
|