synergyspec-selfevolving 1.3.0 → 2.0.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/README.md +50 -19
- package/dist/commands/learn.d.ts +12 -1
- package/dist/commands/learn.js +373 -31
- package/dist/commands/self-evolution-episode.d.ts +177 -0
- package/dist/commands/self-evolution-episode.js +423 -0
- package/dist/commands/self-evolution.d.ts +12 -190
- package/dist/commands/self-evolution.js +179 -786
- package/dist/commands/workflow/status.js +3 -1
- package/dist/core/archive.d.ts +0 -1
- package/dist/core/archive.js +0 -58
- package/dist/core/artifact-graph/instruction-loader.d.ts +2 -4
- package/dist/core/artifact-graph/instruction-loader.js +3 -31
- package/dist/core/config-prompts.js +4 -0
- package/dist/core/fitness/health/health-metrics.d.ts +26 -56
- package/dist/core/fitness/health/health-metrics.js +19 -58
- package/dist/core/fitness/health/index.d.ts +15 -2
- package/dist/core/fitness/health/index.js +25 -1
- package/dist/core/fitness/health/local-source.d.ts +43 -4
- package/dist/core/fitness/health/local-source.js +181 -25
- package/dist/core/fitness/health/metric-source.d.ts +48 -19
- package/dist/core/fitness/health/metric-source.js +8 -18
- package/dist/core/fitness/health/resolve-source.js +4 -1
- package/dist/core/fitness/loss.d.ts +7 -7
- package/dist/core/fitness/loss.js +6 -6
- package/dist/core/fitness/sample.d.ts +10 -0
- package/dist/core/fitness/test-failures.d.ts +30 -0
- package/dist/core/fitness/test-failures.js +123 -0
- package/dist/core/learn/credit-path.d.ts +36 -0
- package/dist/core/learn/credit-path.js +198 -0
- package/dist/core/learn/trajectory-discovery.d.ts +39 -0
- package/dist/core/learn/trajectory-discovery.js +140 -0
- package/dist/core/learn.d.ts +39 -5
- package/dist/core/learn.js +131 -14
- package/dist/core/project-config.d.ts +4 -0
- package/dist/core/project-config.js +52 -1
- package/dist/core/self-evolution/candidate-fitness.d.ts +23 -1
- package/dist/core/self-evolution/candidate-fitness.js +31 -5
- package/dist/core/self-evolution/candidates.d.ts +0 -9
- package/dist/core/self-evolution/canonical-targets.d.ts +8 -4
- package/dist/core/self-evolution/canonical-targets.js +8 -4
- package/dist/core/self-evolution/critic-agent.d.ts +150 -0
- package/dist/core/self-evolution/critic-agent.js +487 -0
- package/dist/core/self-evolution/edits-contract.d.ts +53 -0
- package/dist/core/self-evolution/edits-contract.js +89 -0
- package/dist/core/self-evolution/episode-orchestrator.d.ts +197 -0
- package/dist/core/self-evolution/episode-orchestrator.js +534 -0
- package/dist/core/self-evolution/episode-store.d.ts +266 -0
- package/dist/core/self-evolution/episode-store.js +573 -0
- package/dist/core/self-evolution/evolution-switches.d.ts +1 -1
- package/dist/core/self-evolution/evolution-switches.js +5 -10
- package/dist/core/self-evolution/evolving-agent.d.ts +162 -0
- package/dist/core/self-evolution/evolving-agent.js +449 -0
- package/dist/core/self-evolution/health-baseline.d.ts +25 -6
- package/dist/core/self-evolution/health-baseline.js +30 -6
- package/dist/core/self-evolution/host-harness.d.ts +1 -2
- package/dist/core/self-evolution/host-harness.js +1 -2
- package/dist/core/self-evolution/index.d.ts +10 -6
- package/dist/core/self-evolution/index.js +19 -6
- package/dist/core/self-evolution/learn-hints.d.ts +31 -0
- package/dist/core/self-evolution/learn-hints.js +16 -0
- package/dist/core/self-evolution/learn-observation-adapter.d.ts +35 -0
- package/dist/core/self-evolution/learn-observation-adapter.js +285 -10
- package/dist/core/self-evolution/line-diff.d.ts +60 -0
- package/dist/core/self-evolution/line-diff.js +130 -0
- package/dist/core/self-evolution/policy/fs-safe.d.ts +19 -0
- package/dist/core/self-evolution/policy/fs-safe.js +89 -0
- package/dist/core/self-evolution/policy/index.d.ts +13 -0
- package/dist/core/self-evolution/policy/index.js +13 -0
- package/dist/core/self-evolution/policy/policy-store.d.ts +217 -0
- package/dist/core/self-evolution/policy/policy-store.js +774 -0
- package/dist/core/self-evolution/policy/reject-buffer.d.ts +48 -0
- package/dist/core/self-evolution/policy/reject-buffer.js +168 -0
- package/dist/core/self-evolution/promote.d.ts +1 -1
- package/dist/core/self-evolution/promote.js +6 -33
- package/dist/core/self-evolution/promotion.js +1 -2
- package/dist/core/self-evolution/proposer-agent.d.ts +41 -0
- package/dist/core/self-evolution/proposer-agent.js +94 -13
- package/dist/core/self-evolution/proposer-slice.d.ts +26 -0
- package/dist/core/self-evolution/proposer-slice.js +54 -0
- package/dist/core/self-evolution/reward-agent.d.ts +234 -0
- package/dist/core/self-evolution/reward-agent.js +564 -0
- package/dist/core/self-evolution/scope-gate.d.ts +66 -0
- package/dist/core/self-evolution/scope-gate.js +107 -0
- package/dist/core/self-evolution/success-channel.d.ts +79 -0
- package/dist/core/self-evolution/success-channel.js +361 -0
- package/dist/core/self-evolution/target-evolution.d.ts +11 -0
- package/dist/core/self-evolution/target-evolution.js +2 -0
- package/dist/core/self-evolution/tool-evolution.js +2 -13
- package/dist/core/self-evolution/verdict.d.ts +8 -5
- package/dist/core/self-evolution/verdict.js +4 -7
- package/dist/core/templates/skill-templates.d.ts +1 -0
- package/dist/core/templates/skill-templates.js +1 -0
- package/dist/core/templates/workflow-manifest.js +2 -0
- package/dist/core/templates/workflows/learn.d.ts +4 -2
- package/dist/core/templates/workflows/learn.js +25 -166
- package/dist/core/templates/workflows/self-evolving.d.ts +13 -0
- package/dist/core/templates/workflows/self-evolving.js +127 -0
- package/dist/core/trajectory/facts.d.ts +16 -0
- package/dist/core/trajectory/facts.js +12 -4
- package/dist/core/trajectory/skeleton.d.ts +43 -0
- package/dist/core/trajectory/skeleton.js +239 -0
- package/dist/dashboard/data.d.ts +25 -51
- package/dist/dashboard/data.js +68 -180
- package/dist/dashboard/react-client.js +458 -503
- package/dist/dashboard/react-styles.js +3 -3
- package/dist/dashboard/server.js +23 -17
- package/dist/ui/ascii-patterns.d.ts +7 -15
- package/dist/ui/ascii-patterns.js +123 -54
- package/dist/ui/welcome-screen.d.ts +0 -14
- package/dist/ui/welcome-screen.js +16 -35
- package/package.json +3 -1
- package/scripts/code-health.py +1066 -638
- package/scripts/slop_rules.yaml +2151 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 范围⊆诊断 scope⊆diagnosis gate for the 演进智能体 EVOLVING AGENT — loop v2
|
|
3
|
+
* (self-evolution as in-context RL).
|
|
4
|
+
*
|
|
5
|
+
* The 奖励智能体 REWARD AGENT names a set of GAPS, each anchored to a (file,
|
|
6
|
+
* section) the 文本梯度 textual gradient points at. The EVOLVING AGENT's ONE
|
|
7
|
+
* bounded edit must stay INSIDE those named sections — it may not wander off
|
|
8
|
+
* and rewrite an unrelated heading just because the file is editable. This gate
|
|
9
|
+
* is the check: from the line diff, compute each changed range's ENCLOSING
|
|
10
|
+
* section, and PASS iff every (file, section) the edit touches is covered by
|
|
11
|
+
* some diagnosis gap.
|
|
12
|
+
*
|
|
13
|
+
* Section addressing:
|
|
14
|
+
* - `.md` files: the nearest PRECEDING markdown heading of any `#`-level
|
|
15
|
+
* (`# …`, `## …`, …). A change before the first heading has section `''`
|
|
16
|
+
* (the file preamble).
|
|
17
|
+
* - YAML / other files: the nearest preceding TOP-LEVEL key (`key:` at column
|
|
18
|
+
* 0). A change before the first top-level key has section `''`.
|
|
19
|
+
*
|
|
20
|
+
* Coverage:
|
|
21
|
+
* - a gap `{file: '*'}` covers ANY file;
|
|
22
|
+
* - a gap `{section: '*'}` covers the WHOLE file;
|
|
23
|
+
* - otherwise the gap's `file` AND `section` must match exactly.
|
|
24
|
+
*
|
|
25
|
+
* Pure + dependency-free (golden-testable). Re-uses {@link lineDiff} so its
|
|
26
|
+
* notion of "changed lines" is identical to the ≤ L budget check.
|
|
27
|
+
*/
|
|
28
|
+
import { lineDiff } from './line-diff.js';
|
|
29
|
+
function toPosix(p) {
|
|
30
|
+
return p.replace(/\\/g, '/');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The enclosing section of 1-based `line` in `proposedContent`. For `.md`
|
|
34
|
+
* files it is the nearest preceding heading's text; otherwise the nearest
|
|
35
|
+
* preceding top-level (`key:` at column 0) key. `''` when nothing precedes it.
|
|
36
|
+
*/
|
|
37
|
+
function enclosingSection(relPath, proposedLines, line) {
|
|
38
|
+
const isMarkdown = /\.md$/i.test(relPath);
|
|
39
|
+
let section = '';
|
|
40
|
+
// Scan from the file top down to (and including) the changed line; the last
|
|
41
|
+
// matching anchor at-or-before it is the enclosing section.
|
|
42
|
+
const upto = Math.min(line, proposedLines.length);
|
|
43
|
+
for (let idx = 0; idx < upto; idx++) {
|
|
44
|
+
const text = proposedLines[idx];
|
|
45
|
+
if (isMarkdown) {
|
|
46
|
+
const h = /^#{1,6}\s+(.+?)\s*$/.exec(text);
|
|
47
|
+
if (h)
|
|
48
|
+
section = h[1];
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Top-level key: an identifier-ish key at column 0 followed by ':'.
|
|
52
|
+
const k = /^([^\s:#][^:]*):(?:\s|$)/.exec(text);
|
|
53
|
+
if (k)
|
|
54
|
+
section = k[1].trim();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return section;
|
|
58
|
+
}
|
|
59
|
+
/** True iff some gap covers `(file, section)` per the wildcard rules. */
|
|
60
|
+
function isCovered(file, section, gaps) {
|
|
61
|
+
for (const gap of gaps) {
|
|
62
|
+
const fileOk = gap.file === '*' || toPosix(gap.file) === file;
|
|
63
|
+
if (!fileOk)
|
|
64
|
+
continue;
|
|
65
|
+
if (gap.section === '*' || gap.section === section)
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Run the 范围⊆诊断 scope⊆diagnosis gate. For every edit, diff it against its
|
|
72
|
+
* current content, resolve each changed range's enclosing section, and flag any
|
|
73
|
+
* (file, section) not covered by a gap. Returns `{pass, violations}`; a pure
|
|
74
|
+
* deletion (no inserted range) is in-scope by construction (it removes named or
|
|
75
|
+
* unnamed lines but introduces no new out-of-scope section) and is not flagged.
|
|
76
|
+
*/
|
|
77
|
+
export function checkScopeWithinDiagnosis(input) {
|
|
78
|
+
const currentByPath = new Map(input.currentFiles.map((f) => [toPosix(f.relPath), f.content]));
|
|
79
|
+
// Accumulate violations keyed by file+section so the lines coalesce per spot.
|
|
80
|
+
const violationByKey = new Map();
|
|
81
|
+
for (const edit of input.edits) {
|
|
82
|
+
const rel = toPosix(edit.relPath);
|
|
83
|
+
const current = currentByPath.get(rel) ?? '';
|
|
84
|
+
const proposedLines = edit.content.length === 0 ? [] : edit.content.replace(/\n$/, '').split('\n');
|
|
85
|
+
const d = lineDiff(current, edit.content);
|
|
86
|
+
for (const range of d.changedRanges) {
|
|
87
|
+
const section = enclosingSection(rel, proposedLines, range.startLine);
|
|
88
|
+
if (isCovered(rel, section, input.gaps))
|
|
89
|
+
continue;
|
|
90
|
+
const key = `${rel}${section}`;
|
|
91
|
+
const existing = violationByKey.get(key);
|
|
92
|
+
if (existing) {
|
|
93
|
+
existing.lines.push({ startLine: range.startLine, endLine: range.endLine });
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
violationByKey.set(key, {
|
|
97
|
+
file: rel,
|
|
98
|
+
section,
|
|
99
|
+
lines: [{ startLine: range.startLine, endLine: range.endLine }],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const violations = [...violationByKey.values()];
|
|
105
|
+
return { pass: violations.length === 0, violations };
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=scope-gate.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { LearnReport } from '../learn.js';
|
|
2
|
+
export declare const PROTECTIONS_FILE = "protections.json";
|
|
3
|
+
/** One protected surface: a section of a canonical target implicated in green runs. */
|
|
4
|
+
export interface TargetProtection {
|
|
5
|
+
/** Producing canonical target id (e.g. `artifact-template:design`). */
|
|
6
|
+
targetId: string;
|
|
7
|
+
/** Design-section heading the credit path resolved, or `'*'` for the whole target. */
|
|
8
|
+
section: string;
|
|
9
|
+
/** Unique change names whose verified-green runs implicated this section. */
|
|
10
|
+
sourceChanges: string[];
|
|
11
|
+
/** Count of verified-green runs (mining calls) that implicated this section. */
|
|
12
|
+
occurrences: number;
|
|
13
|
+
/** ISO-8601 timestamp of the most recent implicating run. */
|
|
14
|
+
lastObservedAt: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MineSuccessSignalsResult {
|
|
17
|
+
/** True iff the report was verified-GREEN and the channel ran. */
|
|
18
|
+
mined: boolean;
|
|
19
|
+
/** Protections recorded/incremented this call (0 when not green or persist failed). */
|
|
20
|
+
protectionsWritten: number;
|
|
21
|
+
/** Absolute paths of exemplar files written this call. */
|
|
22
|
+
exemplarsWritten: string[];
|
|
23
|
+
/** Target ids that received a protection this call (for one-line summaries). */
|
|
24
|
+
protectedTargets: string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The success-channel "green" predicate. DELIBERATELY the same predicate shape
|
|
28
|
+
* as the observed-verified gate conditions in `promote.ts` `isEvidenceComplete`
|
|
29
|
+
* (trajectoryFacts.verified === true AND observedStatus === 'success' OR
|
|
30
|
+
* observedPassRate >= 1) — read that function first when changing this one, so
|
|
31
|
+
* success-mining and the gate can never disagree about what "green" means.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isVerifiedGreen(report: LearnReport): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Read the persisted protections for one canonical target. Defensive read —
|
|
36
|
+
* absent/corrupt file means "no protections yet", never an error.
|
|
37
|
+
*/
|
|
38
|
+
export declare function readProtections(projectRoot: string, targetId: string): Promise<TargetProtection[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Exemplar file paths for a target, most-recent first (mtime desc, then
|
|
41
|
+
* filename desc as the deterministic tiebreak — the same order the retention
|
|
42
|
+
* cap uses). `[]` when the dir is absent.
|
|
43
|
+
*/
|
|
44
|
+
export declare function listExemplarFiles(projectRoot: string, targetId: string): Promise<string[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Parse the ✅-status rows of the spec-tests.md Requirement Traceability
|
|
47
|
+
* Matrix and return their Test Case files. Only rows INSIDE the
|
|
48
|
+
* `## Requirement Traceability Matrix` section count (the PBT Coverage table
|
|
49
|
+
* shares the pipe/✅ shape but its test column is scenario-level). The status
|
|
50
|
+
* cell is located by content (contains '✅') and the Test Case cell is the one
|
|
51
|
+
* just before it, so benign column drift does not blank the channel.
|
|
52
|
+
*/
|
|
53
|
+
export declare function passingTestFilesFromMatrix(specTestsMd: string): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Render the bounded DO-NOT-PRUNE block: one line per protected section with
|
|
56
|
+
* its passing-run count, plus the exemplar file paths. Empty string when there
|
|
57
|
+
* is nothing to protect. Deterministic: sorted by occurrences desc, then
|
|
58
|
+
* targetId/section.
|
|
59
|
+
*/
|
|
60
|
+
export declare function renderDoNotPruneBlock(protections: TargetProtection[], exemplarPaths: string[]): string;
|
|
61
|
+
/**
|
|
62
|
+
* Mine a verified-GREEN learn report for protections + exemplars.
|
|
63
|
+
*
|
|
64
|
+
* NOT green ⇒ `{ mined: false }` and NOTHING is written — the success channel
|
|
65
|
+
* must never manufacture signal from an unverified or failing run (the exact
|
|
66
|
+
* inverse discipline of the observed-verified promote gate).
|
|
67
|
+
*
|
|
68
|
+
* Green ⇒ walk each ✅ matrix row's test back through the credit path
|
|
69
|
+
* (test → UC → task → design section), protect the producing target's section,
|
|
70
|
+
* persist merged protections, and write bounded real-excerpt exemplars
|
|
71
|
+
* (3 most-recent kept per target).
|
|
72
|
+
*/
|
|
73
|
+
export declare function mineSuccessSignals(opts: {
|
|
74
|
+
projectRoot: string;
|
|
75
|
+
report: LearnReport;
|
|
76
|
+
/** Injectable clock for tests; defaults to the wall clock. */
|
|
77
|
+
now?: () => Date;
|
|
78
|
+
}): Promise<MineSuccessSignalsResult>;
|
|
79
|
+
//# sourceMappingURL=success-channel.d.ts.map
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SUCCESS CHANNEL (R4: "the fence is the feature").
|
|
3
|
+
*
|
|
4
|
+
* The failure path mines red runs for corrective evolution hints; this module
|
|
5
|
+
* is its mirror: a verified-GREEN run is mined for (a) load-bearing
|
|
6
|
+
* PROTECTIONS — the design sections / tasks the passing tests' credit paths
|
|
7
|
+
* implicate as producers of working behavior — and (b) EXEMPLARS — bounded
|
|
8
|
+
* REAL excerpts of those artifacts. Both flow into the proposer surfaces as
|
|
9
|
+
* DO-NOT-PRUNE constraints, so an evolution candidate can never "improve" a
|
|
10
|
+
* template by hollowing out the parts that demonstrably produce green runs.
|
|
11
|
+
*
|
|
12
|
+
* Crucially this is a SIDE-WRITE channel only: a clean run still NEVER authors
|
|
13
|
+
* or triggers a corrective candidate (abstain-on-success is preserved — the
|
|
14
|
+
* channel writes protections/exemplars, never anything under `candidates/`).
|
|
15
|
+
*
|
|
16
|
+
* Persistence (best-effort, mirroring `health-baseline.ts`): a missing/corrupt
|
|
17
|
+
* protections file reads as empty, and a failed write never fails the learn
|
|
18
|
+
* run that triggered the mining.
|
|
19
|
+
*/
|
|
20
|
+
import { promises as fs } from 'node:fs';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
import { walkCreditPath } from '../learn/credit-path.js';
|
|
23
|
+
import { producingTargetIdForCreditPath } from './learn-observation-adapter.js';
|
|
24
|
+
export const PROTECTIONS_FILE = 'protections.json';
|
|
25
|
+
/**
|
|
26
|
+
* The success-channel "green" predicate. DELIBERATELY the same predicate shape
|
|
27
|
+
* as the observed-verified gate conditions in `promote.ts` `isEvidenceComplete`
|
|
28
|
+
* (trajectoryFacts.verified === true AND observedStatus === 'success' OR
|
|
29
|
+
* observedPassRate >= 1) — read that function first when changing this one, so
|
|
30
|
+
* success-mining and the gate can never disagree about what "green" means.
|
|
31
|
+
*/
|
|
32
|
+
export function isVerifiedGreen(report) {
|
|
33
|
+
const facts = report.fitnessSample?.trajectoryFacts;
|
|
34
|
+
if (!facts || facts.verified !== true)
|
|
35
|
+
return false;
|
|
36
|
+
const greenByStatus = facts.observedStatus === 'success';
|
|
37
|
+
const greenByPassRate = typeof facts.observedPassRate === 'number' && facts.observedPassRate >= 1;
|
|
38
|
+
return greenByStatus || greenByPassRate;
|
|
39
|
+
}
|
|
40
|
+
function selfEvolutionDir(projectRoot) {
|
|
41
|
+
return path.join(path.resolve(projectRoot), '.synergyspec-selfevolving', 'self-evolution');
|
|
42
|
+
}
|
|
43
|
+
function protectionsPath(projectRoot) {
|
|
44
|
+
return path.join(selfEvolutionDir(projectRoot), PROTECTIONS_FILE);
|
|
45
|
+
}
|
|
46
|
+
/** Exemplar dir for a target — `:` is not portable in dir names, so `__`. */
|
|
47
|
+
function exemplarDir(projectRoot, targetId) {
|
|
48
|
+
return path.join(selfEvolutionDir(projectRoot), 'exemplars', targetId.replace(/:/g, '__'));
|
|
49
|
+
}
|
|
50
|
+
async function readFileOrUndefined(abs) {
|
|
51
|
+
try {
|
|
52
|
+
return await fs.readFile(abs, 'utf8');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function isValidProtection(value) {
|
|
59
|
+
if (!value || typeof value !== 'object')
|
|
60
|
+
return false;
|
|
61
|
+
const p = value;
|
|
62
|
+
return (typeof p.targetId === 'string' &&
|
|
63
|
+
typeof p.section === 'string' &&
|
|
64
|
+
Array.isArray(p.sourceChanges) &&
|
|
65
|
+
p.sourceChanges.every((c) => typeof c === 'string') &&
|
|
66
|
+
typeof p.occurrences === 'number' &&
|
|
67
|
+
Number.isFinite(p.occurrences) &&
|
|
68
|
+
typeof p.lastObservedAt === 'string');
|
|
69
|
+
}
|
|
70
|
+
/** Read ALL persisted protections; `[]` when the file is absent/unparseable. */
|
|
71
|
+
async function readAllProtections(projectRoot) {
|
|
72
|
+
const raw = await readFileOrUndefined(protectionsPath(projectRoot));
|
|
73
|
+
if (raw === undefined)
|
|
74
|
+
return [];
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(raw);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
if (!parsed || typeof parsed !== 'object')
|
|
83
|
+
return [];
|
|
84
|
+
const list = parsed.protections;
|
|
85
|
+
if (!Array.isArray(list))
|
|
86
|
+
return [];
|
|
87
|
+
return list.filter(isValidProtection);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Read the persisted protections for one canonical target. Defensive read —
|
|
91
|
+
* absent/corrupt file means "no protections yet", never an error.
|
|
92
|
+
*/
|
|
93
|
+
export async function readProtections(projectRoot, targetId) {
|
|
94
|
+
return (await readAllProtections(projectRoot)).filter((p) => p.targetId === targetId);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Exemplar file paths for a target, most-recent first (mtime desc, then
|
|
98
|
+
* filename desc as the deterministic tiebreak — the same order the retention
|
|
99
|
+
* cap uses). `[]` when the dir is absent.
|
|
100
|
+
*/
|
|
101
|
+
export async function listExemplarFiles(projectRoot, targetId) {
|
|
102
|
+
const dir = exemplarDir(projectRoot, targetId);
|
|
103
|
+
let names;
|
|
104
|
+
try {
|
|
105
|
+
names = (await fs.readdir(dir)).filter((n) => n.endsWith('.md'));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const stats = [];
|
|
111
|
+
for (const name of names) {
|
|
112
|
+
const abs = path.join(dir, name);
|
|
113
|
+
try {
|
|
114
|
+
const st = await fs.stat(abs);
|
|
115
|
+
stats.push({ abs, name, mtimeMs: st.mtimeMs });
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// raced deletion — skip
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
stats.sort((a, b) => b.mtimeMs - a.mtimeMs || b.name.localeCompare(a.name));
|
|
122
|
+
return stats.map((s) => s.abs);
|
|
123
|
+
}
|
|
124
|
+
/** Keep only the `keep` most-recent exemplar files in a target dir. */
|
|
125
|
+
const EXEMPLARS_KEPT_PER_TARGET = 3;
|
|
126
|
+
/** Per-excerpt caps for the exemplar substrate (real text, bounded). */
|
|
127
|
+
const DESIGN_EXCERPT_CAP = 1500;
|
|
128
|
+
const TASK_EXCERPT_CAP = 500;
|
|
129
|
+
function capText(text, max) {
|
|
130
|
+
const t = text.trim();
|
|
131
|
+
return t.length > max ? `${t.slice(0, max - 1)}…` : t;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Parse the ✅-status rows of the spec-tests.md Requirement Traceability
|
|
135
|
+
* Matrix and return their Test Case files. Only rows INSIDE the
|
|
136
|
+
* `## Requirement Traceability Matrix` section count (the PBT Coverage table
|
|
137
|
+
* shares the pipe/✅ shape but its test column is scenario-level). The status
|
|
138
|
+
* cell is located by content (contains '✅') and the Test Case cell is the one
|
|
139
|
+
* just before it, so benign column drift does not blank the channel.
|
|
140
|
+
*/
|
|
141
|
+
export function passingTestFilesFromMatrix(specTestsMd) {
|
|
142
|
+
const lines = specTestsMd.split(/\r?\n/);
|
|
143
|
+
const files = [];
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
let inMatrix = false;
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
const heading = line.match(/^(#{2,3})\s+(.+)$/);
|
|
148
|
+
if (heading) {
|
|
149
|
+
inMatrix = /requirement traceability matrix/i.test(heading[2]);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (!inMatrix || !line.trimStart().startsWith('|'))
|
|
153
|
+
continue;
|
|
154
|
+
const cells = line.split('|').map((c) => c.trim());
|
|
155
|
+
const statusIdx = cells.findIndex((c) => c.includes('✅'));
|
|
156
|
+
if (statusIdx <= 0)
|
|
157
|
+
continue;
|
|
158
|
+
const testCell = cells[statusIdx - 1] ?? '';
|
|
159
|
+
for (const token of testCell.split(',')) {
|
|
160
|
+
// Strip backticks and a trailing `:line` suffix; keep only path-shaped tokens.
|
|
161
|
+
const file = token.replace(/`/g, '').trim().replace(/:\d+$/, '');
|
|
162
|
+
if (file.length === 0 || !/[\\/.]/.test(file))
|
|
163
|
+
continue;
|
|
164
|
+
if (seen.has(file))
|
|
165
|
+
continue;
|
|
166
|
+
seen.add(file);
|
|
167
|
+
files.push(file);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return files;
|
|
171
|
+
}
|
|
172
|
+
/** Render cap for the DO-NOT-PRUNE block fed into prompts / CLI output. */
|
|
173
|
+
const DO_NOT_PRUNE_BLOCK_CAP = 1200;
|
|
174
|
+
/** Short human label for a target id inside the rendered block. */
|
|
175
|
+
function targetLabel(targetId) {
|
|
176
|
+
return targetId.startsWith('artifact-template:')
|
|
177
|
+
? targetId.slice('artifact-template:'.length)
|
|
178
|
+
: targetId;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Render the bounded DO-NOT-PRUNE block: one line per protected section with
|
|
182
|
+
* its passing-run count, plus the exemplar file paths. Empty string when there
|
|
183
|
+
* is nothing to protect. Deterministic: sorted by occurrences desc, then
|
|
184
|
+
* targetId/section.
|
|
185
|
+
*/
|
|
186
|
+
export function renderDoNotPruneBlock(protections, exemplarPaths) {
|
|
187
|
+
if (protections.length === 0)
|
|
188
|
+
return '';
|
|
189
|
+
const sorted = [...protections].sort((a, b) => b.occurrences - a.occurrences ||
|
|
190
|
+
a.targetId.localeCompare(b.targetId) ||
|
|
191
|
+
a.section.localeCompare(b.section));
|
|
192
|
+
const lines = [];
|
|
193
|
+
for (const p of sorted) {
|
|
194
|
+
const where = p.section === '*' ? `${targetLabel(p.targetId)} (whole target)` : `${targetLabel(p.targetId)} §"${p.section}"`;
|
|
195
|
+
lines.push(`- ${where} — implicated in ${p.occurrences} passing run(s); do not delete or hollow out`);
|
|
196
|
+
}
|
|
197
|
+
if (exemplarPaths.length > 0) {
|
|
198
|
+
lines.push(`exemplars: ${exemplarPaths.join(', ')}`);
|
|
199
|
+
}
|
|
200
|
+
const block = lines.join('\n');
|
|
201
|
+
return block.length > DO_NOT_PRUNE_BLOCK_CAP
|
|
202
|
+
? `${block.slice(0, DO_NOT_PRUNE_BLOCK_CAP - 1)}…`
|
|
203
|
+
: block;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Mine a verified-GREEN learn report for protections + exemplars.
|
|
207
|
+
*
|
|
208
|
+
* NOT green ⇒ `{ mined: false }` and NOTHING is written — the success channel
|
|
209
|
+
* must never manufacture signal from an unverified or failing run (the exact
|
|
210
|
+
* inverse discipline of the observed-verified promote gate).
|
|
211
|
+
*
|
|
212
|
+
* Green ⇒ walk each ✅ matrix row's test back through the credit path
|
|
213
|
+
* (test → UC → task → design section), protect the producing target's section,
|
|
214
|
+
* persist merged protections, and write bounded real-excerpt exemplars
|
|
215
|
+
* (3 most-recent kept per target).
|
|
216
|
+
*/
|
|
217
|
+
export async function mineSuccessSignals(opts) {
|
|
218
|
+
const { projectRoot, report } = opts;
|
|
219
|
+
if (!isVerifiedGreen(report)) {
|
|
220
|
+
return { mined: false, protectionsWritten: 0, exemplarsWritten: [], protectedTargets: [] };
|
|
221
|
+
}
|
|
222
|
+
const nowIso = (opts.now ? opts.now() : new Date()).toISOString();
|
|
223
|
+
// Read the change's artifacts straight from the change dir (tolerate absence:
|
|
224
|
+
// a missing artifact just truncates the walk, same as the failure path).
|
|
225
|
+
const specTestsMd = await readFileOrUndefined(path.join(report.changeDir, 'spec-tests.md'));
|
|
226
|
+
const tasksMd = await readFileOrUndefined(path.join(report.changeDir, 'tasks.md'));
|
|
227
|
+
const designMd = await readFileOrUndefined(path.join(report.changeDir, 'design.md'));
|
|
228
|
+
// Walk every passing test's credit path to its producing target + section.
|
|
229
|
+
const walkedPaths = [];
|
|
230
|
+
const minedKeys = new Map();
|
|
231
|
+
const protect = (targetId, section) => {
|
|
232
|
+
const key = `${targetId} ${section}`;
|
|
233
|
+
if (!minedKeys.has(key))
|
|
234
|
+
minedKeys.set(key, { targetId, section });
|
|
235
|
+
};
|
|
236
|
+
if (specTestsMd) {
|
|
237
|
+
for (const testFile of passingTestFilesFromMatrix(specTestsMd)) {
|
|
238
|
+
const creditPath = walkCreditPath({ testId: testFile, specTestsMd, tasksMd, designMd });
|
|
239
|
+
const targetId = producingTargetIdForCreditPath(creditPath);
|
|
240
|
+
if (!targetId)
|
|
241
|
+
continue;
|
|
242
|
+
walkedPaths.push(creditPath);
|
|
243
|
+
const designNode = creditPath.nodes.find((n) => n.kind === 'design-section');
|
|
244
|
+
// The design heading is the protected SECTION; a path that ended at a
|
|
245
|
+
// task protects the whole tasks template (no finer address exists).
|
|
246
|
+
protect(targetId, designNode ? designNode.id : '*');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Whole-target protections: a green run whose tasks were ≥80% UC-mapped
|
|
250
|
+
// demonstrates the design/tasks templates produced a traceable, working
|
|
251
|
+
// decomposition — protect them as wholes even without per-test paths.
|
|
252
|
+
if (report.artifacts.taskProgress.mappingRatio >= 0.8) {
|
|
253
|
+
if (designMd !== undefined)
|
|
254
|
+
protect('artifact-template:design', '*');
|
|
255
|
+
if (tasksMd !== undefined)
|
|
256
|
+
protect('artifact-template:tasks', '*');
|
|
257
|
+
}
|
|
258
|
+
const mined = [...minedKeys.values()];
|
|
259
|
+
if (mined.length === 0) {
|
|
260
|
+
return { mined: true, protectionsWritten: 0, exemplarsWritten: [], protectedTargets: [] };
|
|
261
|
+
}
|
|
262
|
+
// MERGE with the persisted file: dedupe by targetId+section, increment
|
|
263
|
+
// occurrences once per mining call, track source changes uniquely.
|
|
264
|
+
const existing = await readAllProtections(projectRoot);
|
|
265
|
+
for (const m of mined) {
|
|
266
|
+
const found = existing.find((p) => p.targetId === m.targetId && p.section === m.section);
|
|
267
|
+
if (found) {
|
|
268
|
+
found.occurrences += 1;
|
|
269
|
+
if (!found.sourceChanges.includes(report.changeName)) {
|
|
270
|
+
found.sourceChanges.push(report.changeName);
|
|
271
|
+
}
|
|
272
|
+
found.lastObservedAt = nowIso;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
existing.push({
|
|
276
|
+
targetId: m.targetId,
|
|
277
|
+
section: m.section,
|
|
278
|
+
sourceChanges: [report.changeName],
|
|
279
|
+
occurrences: 1,
|
|
280
|
+
lastObservedAt: nowIso,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Best-effort persist (health-baseline.ts pattern): a failed side-write must
|
|
285
|
+
// never fail the learn/episode run that triggered the mining.
|
|
286
|
+
let protectionsWritten = 0;
|
|
287
|
+
try {
|
|
288
|
+
const file = protectionsPath(projectRoot);
|
|
289
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
290
|
+
const payload = { version: 1, protections: existing };
|
|
291
|
+
await fs.writeFile(file, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
292
|
+
protectionsWritten = mined.length;
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// swallowed: side-write only
|
|
296
|
+
}
|
|
297
|
+
// EXEMPLARS: per protected target, persist the REAL excerpts the walked
|
|
298
|
+
// paths carried (the substrate the proposer's EXEMPLARS block quotes).
|
|
299
|
+
const exemplarsWritten = [];
|
|
300
|
+
const protectedTargets = [...new Set(mined.map((m) => m.targetId))];
|
|
301
|
+
const loss = report.fitnessSample?.loss?.loss;
|
|
302
|
+
for (const targetId of protectedTargets) {
|
|
303
|
+
const sections = [];
|
|
304
|
+
const seenExcerpts = new Set();
|
|
305
|
+
const pushExcerpt = (label, text, cap) => {
|
|
306
|
+
if (!text)
|
|
307
|
+
return;
|
|
308
|
+
const capped = capText(text, cap);
|
|
309
|
+
if (capped.length === 0 || seenExcerpts.has(capped))
|
|
310
|
+
return;
|
|
311
|
+
seenExcerpts.add(capped);
|
|
312
|
+
sections.push(`## ${label}\n\n${capped}`);
|
|
313
|
+
};
|
|
314
|
+
if (targetId === 'artifact-template:design') {
|
|
315
|
+
for (const p of walkedPaths) {
|
|
316
|
+
const design = p.nodes.find((n) => n.kind === 'design-section');
|
|
317
|
+
if (design)
|
|
318
|
+
pushExcerpt(`design §"${design.id}"`, design.excerpt, DESIGN_EXCERPT_CAP);
|
|
319
|
+
}
|
|
320
|
+
// One task block for context: the first walked path's task hop.
|
|
321
|
+
const task = walkedPaths.flatMap((p) => p.nodes).find((n) => n.kind === 'task');
|
|
322
|
+
if (task)
|
|
323
|
+
pushExcerpt(`task ${task.id}`, task.excerpt, TASK_EXCERPT_CAP);
|
|
324
|
+
}
|
|
325
|
+
else if (targetId === 'artifact-template:tasks') {
|
|
326
|
+
for (const p of walkedPaths) {
|
|
327
|
+
const task = p.nodes.find((n) => n.kind === 'task');
|
|
328
|
+
if (task)
|
|
329
|
+
pushExcerpt(`task ${task.id}`, task.excerpt, TASK_EXCERPT_CAP);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (sections.length === 0)
|
|
333
|
+
continue;
|
|
334
|
+
const header = [
|
|
335
|
+
`# Exemplar: ${report.changeName}`,
|
|
336
|
+
'',
|
|
337
|
+
`- recorded: ${nowIso}`,
|
|
338
|
+
`- loss: ${typeof loss === 'number' ? loss.toFixed(3) : 'unmeasured'}`,
|
|
339
|
+
'',
|
|
340
|
+
].join('\n');
|
|
341
|
+
const dir = exemplarDir(projectRoot, targetId);
|
|
342
|
+
const file = path.join(dir, `${report.changeName}.md`);
|
|
343
|
+
try {
|
|
344
|
+
await fs.mkdir(dir, { recursive: true });
|
|
345
|
+
await fs.writeFile(file, `${header}\n${sections.join('\n\n')}\n`, 'utf8');
|
|
346
|
+
exemplarsWritten.push(file);
|
|
347
|
+
// Retention: keep only the 3 most-recent exemplar files per target
|
|
348
|
+
// (mtime desc with filename-desc tiebreak — deterministic even when a
|
|
349
|
+
// fast filesystem stamps identical mtimes).
|
|
350
|
+
const all = await listExemplarFiles(projectRoot, targetId);
|
|
351
|
+
for (const stale of all.slice(EXEMPLARS_KEPT_PER_TARGET)) {
|
|
352
|
+
await fs.rm(stale, { force: true });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// swallowed: side-write only
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return { mined: true, protectionsWritten, exemplarsWritten, protectedTargets };
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=success-channel.js.map
|
|
@@ -24,6 +24,15 @@ export interface TargetEvolutionPolicy {
|
|
|
24
24
|
default: 'frozen' | 'evolvable';
|
|
25
25
|
/** Per-target overrides: targetId -> evolve. */
|
|
26
26
|
explicit: Map<string, boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* The evolution-focus switch (config `selfEvolution.focus`, default ON;
|
|
29
|
+
* `learn --no-focus` turns it off per-run). When ON and the policy is
|
|
30
|
+
* frozen-by-default with explicit evolvable targets, learn re-aims a change's
|
|
31
|
+
* otherwise-dropped frozen-kind signals at those targets as a synthesized
|
|
32
|
+
* `origin: 'policy-focus'` hint instead of silently dropping them. Optional
|
|
33
|
+
* so hand-built policy literals stay valid; absent ⇒ enabled.
|
|
34
|
+
*/
|
|
35
|
+
focusEnabled?: boolean;
|
|
27
36
|
/** Provenance, for diagnostics / a future `status` view. */
|
|
28
37
|
source: {
|
|
29
38
|
config: boolean;
|
|
@@ -45,6 +54,8 @@ export declare function resolveTargetEvolutionPolicy(input: {
|
|
|
45
54
|
config?: ProjectConfig | null;
|
|
46
55
|
evolveTarget?: string;
|
|
47
56
|
freezeTarget?: string;
|
|
57
|
+
/** CLI focus override (`learn --no-focus` ⇒ false). Beats config when set. */
|
|
58
|
+
focus?: boolean;
|
|
48
59
|
}): TargetEvolutionPolicy;
|
|
49
60
|
/**
|
|
50
61
|
* The single decision used by every enforcement point. Does NOT account for the
|
|
@@ -45,6 +45,8 @@ export function resolveTargetEvolutionPolicy(input) {
|
|
|
45
45
|
return {
|
|
46
46
|
default: dflt,
|
|
47
47
|
explicit,
|
|
48
|
+
// CLI override (--no-focus) > config selfEvolution.focus > default ON.
|
|
49
|
+
focusEnabled: input.focus ?? se?.focus ?? true,
|
|
48
50
|
source: { config: !!se, cliEvolve: input.evolveTarget, cliFreeze: input.freezeTarget },
|
|
49
51
|
};
|
|
50
52
|
}
|
|
@@ -189,12 +189,10 @@ export function classifyEvolvablePart(filePath) {
|
|
|
189
189
|
if (!file)
|
|
190
190
|
return [];
|
|
191
191
|
const parts = [];
|
|
192
|
-
if (file
|
|
193
|
-
file.startsWith('.synergyspec-selfevolving/self-evolution/templates/') ||
|
|
194
|
-
file.startsWith('.synergyspec-selfevolving/self-evolution/template-variants') ||
|
|
192
|
+
if (file.startsWith('.synergyspec-selfevolving/self-evolution/templates/') ||
|
|
195
193
|
file.startsWith('schemas/') ||
|
|
196
194
|
/\/templates\//.test(file)) {
|
|
197
|
-
parts.push('
|
|
195
|
+
parts.push('artifact-templates');
|
|
198
196
|
}
|
|
199
197
|
if (file === 'src/core/self-evolution/archive-memory.ts') {
|
|
200
198
|
parts.push('archive-memory');
|
|
@@ -212,15 +210,6 @@ export function classifyEvolvablePart(filePath) {
|
|
|
212
210
|
file.startsWith('src/memory/extraction/')) {
|
|
213
211
|
parts.push('runtime-memory');
|
|
214
212
|
}
|
|
215
|
-
if (file.startsWith('evolve/src/evolution/') ||
|
|
216
|
-
file.startsWith('evolve/src/proposer/') ||
|
|
217
|
-
file.startsWith('evolve/src/mutators/') ||
|
|
218
|
-
file.startsWith('evolve/src/variant/') ||
|
|
219
|
-
file.startsWith('evolve/src/materialize/') ||
|
|
220
|
-
file.startsWith('evolve/src/benchmark/') ||
|
|
221
|
-
file === 'src/commands/evolve.ts') {
|
|
222
|
-
parts.push('dgm-harness');
|
|
223
|
-
}
|
|
224
213
|
if (file === 'src/core/self-evolution/tool-evolution.ts' ||
|
|
225
214
|
file.startsWith('evolve/src/safety/') ||
|
|
226
215
|
file.startsWith('evolve/test/safety/')) {
|
|
@@ -9,12 +9,15 @@ export declare const VERDICT_FILE = "verdict.json";
|
|
|
9
9
|
* - `gate-failed` — the static gate refused it.
|
|
10
10
|
* - `declined` — an auto-promote predicate said no (non-terminal: the
|
|
11
11
|
* candidate may still be retried later).
|
|
12
|
-
* - `outcompeted` — lost the GA ranking to a sibling variant for the same
|
|
13
|
-
* target (advisory; the candidate's status is untouched).
|
|
14
12
|
*/
|
|
15
|
-
export type CandidateVerdictKind = 'promoted' | 'rolled-back' | 'rejected' | 'gate-failed' | 'declined'
|
|
16
|
-
/**
|
|
17
|
-
|
|
13
|
+
export type CandidateVerdictKind = 'promoted' | 'rolled-back' | 'rejected' | 'gate-failed' | 'declined';
|
|
14
|
+
/**
|
|
15
|
+
* Who/what reached the verdict — for provenance in the trajectory block.
|
|
16
|
+
* Internal enum, not a user-facing capability. (Legacy verdict.json files may
|
|
17
|
+
* carry a now-removed `'auto-evolve'` decider; the unchecked cast on read keeps
|
|
18
|
+
* those readable.)
|
|
19
|
+
*/
|
|
20
|
+
export type CandidateVerdictDecider = 'human' | 'evolve-from-edits' | 'static-gate';
|
|
18
21
|
export interface CandidateVerdictRecord {
|
|
19
22
|
verdict: CandidateVerdictKind;
|
|
20
23
|
/** ISO-8601 UTC timestamp the verdict was reached (caller-supplied). */
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
* Per-candidate promotion VERDICT (the backward-pass label the proposer reads).
|
|
3
3
|
*
|
|
4
4
|
* `fitness-record.jsonl` records HOW a candidate scored; this sidecar records
|
|
5
|
-
* what the loop DECIDED about it — promoted, rejected,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* these labels so the proposer can learn from prior accepted/rejected attempts
|
|
9
|
-
* (OPRO/AlphaEvolve: scored history of past solutions in the meta-prompt).
|
|
5
|
+
* what the loop DECIDED about it — promoted, rejected, etc. — plus the loss it
|
|
6
|
+
* carried at decision time and the baseline it was judged against, so the
|
|
7
|
+
* manual promote / evolve-from-edits path can learn from prior decisions.
|
|
10
8
|
*
|
|
11
9
|
* A candidate has exactly ONE current verdict, so this is a single JSON object
|
|
12
10
|
* (last-write-wins), NOT an append-only log — the full transition history
|
|
@@ -37,8 +35,7 @@ function isValidVerdictKind(value) {
|
|
|
37
35
|
value === 'rolled-back' ||
|
|
38
36
|
value === 'rejected' ||
|
|
39
37
|
value === 'gate-failed' ||
|
|
40
|
-
value === 'declined'
|
|
41
|
-
value === 'outcompeted');
|
|
38
|
+
value === 'declined');
|
|
42
39
|
}
|
|
43
40
|
/**
|
|
44
41
|
* Write (or overwrite) a candidate's current verdict. Atomic tmp+rename inside
|
|
@@ -23,4 +23,5 @@ export { getCiSkillTemplate, getOpsxCiCommandTemplate } from './workflows/ci.js'
|
|
|
23
23
|
export { getOpsxProposeSkillTemplate, getOpsxProposeCommandTemplate } from './workflows/propose.js';
|
|
24
24
|
export { getFeedbackSkillTemplate } from './workflows/feedback.js';
|
|
25
25
|
export { getCompareImagesSkillTemplate } from './workflows/compare-images.js';
|
|
26
|
+
export { getSelfEvolvingSkillTemplate } from './workflows/self-evolving.js';
|
|
26
27
|
//# sourceMappingURL=skill-templates.d.ts.map
|