synergyspec-selfevolving 2.1.0 → 2.1.1

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.
@@ -80,14 +80,30 @@ export async function captureMainArm(opts) {
80
80
  * Whether the episode SKIPS the rollback decision + 演进智能体 EVOLVING AGENT:
81
81
  * the judge 弃权 abstained (no nameable gap), found no gaps, OR returned the
82
82
  * ⑤ `insufficient-signal` verdict (within the A/A noise floor, or a blocked
83
- * tamper). All three mean "do not evolve on this episode".
83
+ * tamper). These mean "do not evolve on this episode".
84
+ *
85
+ * EXCEPTION (cold-start bootstrap): `insufficient-signal` is honored ONLY as a
86
+ * genuine can't-tell — a within-noise-floor result (the baseline ran) or a blocked
87
+ * tamper. On a baseline-skipped episode (policyVersions.baseline === null) there is
88
+ * no comparison to be uncertain about, so a stray `insufficient-signal` emitted
89
+ * alongside real gaps (and no tamper) must NOT block: the first v0→v1 evolution has
90
+ * to be reachable from absolute signal, or a fresh target stays at v0 forever. This
91
+ * is defense-in-depth behind {@link deriveSingleSampleVerdict}, which already drops
92
+ * a volunteered verdict to `undefined` on a baseline skip.
84
93
  */
85
94
  function shouldSkipEvolution(diagnosis) {
86
95
  if (diagnosis === null)
87
96
  return true;
88
- return (diagnosis.abstained ||
89
- diagnosis.gaps.length === 0 ||
90
- diagnosis.verdict === 'insufficient-signal');
97
+ if (diagnosis.abstained || diagnosis.gaps.length === 0)
98
+ return true;
99
+ if (diagnosis.verdict === 'insufficient-signal') {
100
+ const baselineSkipped = diagnosis.policyVersions?.baseline === null;
101
+ const tamper = diagnosis.integrity?.testTamperSuspected ?? false;
102
+ if (baselineSkipped && !tamper)
103
+ return false;
104
+ return true;
105
+ }
106
+ return false;
91
107
  }
92
108
  /**
93
109
  * Count the consecutive trailing rolled-back episodes in the 版本账本 ledger.
@@ -154,6 +154,11 @@ export function assembleRewardAgentPrompt(input) {
154
154
  '(no prior policy version in the 版本账本 ledger, or skipped by policy).',
155
155
  'NO comparison is possible: set "rewardBaseline" to null and "advantage" to',
156
156
  'null. Score ONLY the main arm and diagnose against the artifacts below.',
157
+ 'A comparative verdict is impossible with one arm: do NOT emit "main-better",',
158
+ '"baseline-better", "tie", or "insufficient-signal" (insufficient-signal means',
159
+ 'a genuine can\'t-tell, NOT "no baseline ran"). OMIT "verdict" entirely when you',
160
+ 'can still name a real gap (keep "abstained": false with a "textualGradient");',
161
+ 'use "no-gap" ONLY if the main arm is already clean with nothing to improve.',
157
162
  '',
158
163
  ];
159
164
  }
@@ -849,12 +854,21 @@ export async function runRewardAgent(opts) {
849
854
  * A/A noise floor.
850
855
  */
851
856
  export function deriveSingleSampleVerdict(parsed) {
857
+ // A comparative/insufficient verdict is meaningless without a baseline arm. On a
858
+ // baseline skip (advantage === null — a cold-start episode) IGNORE any verdict the
859
+ // judge volunteered and return `undefined` (or 'no-gap' if it truly abstained), so
860
+ // a real diagnosis (named gaps + a textual gradient) is NOT mis-read as the
861
+ // 'insufficient-signal' abstain that shouldSkipEvolution honors — which would
862
+ // strand a fresh target at v0 forever. This mirrors the aggregated path
863
+ // (advantageMean === null ⇒ verdict undefined). A genuine within-noise-floor or
864
+ // tamper-block 'insufficient-signal' is produced with a baseline present (or
865
+ // re-forced downstream), so it is unaffected.
866
+ if (parsed.advantage === null)
867
+ return parsed.abstained ? 'no-gap' : undefined;
852
868
  if (parsed.verdict !== undefined)
853
869
  return parsed.verdict;
854
870
  if (parsed.abstained)
855
871
  return 'no-gap';
856
- if (parsed.advantage === null)
857
- return undefined;
858
872
  if (parsed.advantage > 1e-9)
859
873
  return 'main-better';
860
874
  if (parsed.advantage < -1e-9)
@@ -1,13 +1,12 @@
1
1
  /**
2
2
  * ASCII art animation patterns for the welcome screen.
3
- * SynergySpec-SelfEvolving logo animation - diamond/rhombus shape with hollow center "O".
4
- */
5
- /**
6
- * Welcome animation frames - SynergySpec-SelfEvolving logo building from center
7
- * 7 rows × 6 columns diamond with hollow center "O"
8
- * Center bar is 2 cols × 3 rows (rows 3,4,5 cols 3,4)
9
- * Each frame is an array of strings (lines of ASCII art)
10
- * Grid: 6 cols × 2 chars = 12 chars wide
3
+ *
4
+ * SynergySpec-SelfEvolving theme: a DNA double-helix that "evolves" across
5
+ * policy versions — the policy version climbs v01 → v08 while a reward bar
6
+ * fills, so the art itself tells the in-context-RL story (the design template
7
+ * 策略/POLICY the main agent's "weights" maturing as reward accrues each
8
+ * episode). The final frame is the fully-evolved helix at policy v08 with a
9
+ * maxed reward readout.
11
10
  */
12
11
  export declare const WELCOME_ANIMATION: {
13
12
  interval: number;
@@ -1,133 +1,67 @@
1
1
  /**
2
2
  * ASCII art animation patterns for the welcome screen.
3
- * SynergySpec-SelfEvolving logo animation - diamond/rhombus shape with hollow center "O".
3
+ *
4
+ * SynergySpec-SelfEvolving theme: a DNA double-helix that "evolves" across
5
+ * policy versions — the policy version climbs v01 → v08 while a reward bar
6
+ * fills, so the art itself tells the in-context-RL story (the design template
7
+ * 策略/POLICY — the main agent's "weights" — maturing as reward accrues each
8
+ * episode). The final frame is the fully-evolved helix at policy v08 with a
9
+ * maxed reward readout.
4
10
  */
5
11
  // Detect if full Unicode is supported
6
12
  const supportsUnicode = process.platform !== 'win32' ||
7
13
  !!process.env.WT_SESSION || // Windows Terminal
8
14
  !!process.env.TERM_PROGRAM; // Modern terminal
9
- // Character set based on Unicode support
10
- // Block characters for pixel-art aesthetic
11
- const CHARS = supportsUnicode
12
- ? { full: '██', dim: '░░', empty: ' ' }
13
- : { full: '##', dim: '++', empty: ' ' };
14
- const _ = CHARS.empty;
15
- const F = CHARS.full;
16
- const D = CHARS.dim;
15
+ // Strand node + reward-bar glyphs (ASCII fallback keeps the `o` node so the
16
+ // helix still reads, and stays within the right-column width budget).
17
+ const NODE = supportsUnicode ? '●' : 'o';
18
+ const LINK = supportsUnicode ? '' : '-';
19
+ const BAR_FULL = supportsUnicode ? '' : '#';
20
+ const BAR_EMPTY = supportsUnicode ? '░' : '.';
21
+ const TOTAL_VERSIONS = 8;
22
+ const REWARD_BAR_WIDTH = 8;
23
+ function pad2(n) {
24
+ return n < 10 ? `0${n}` : String(n);
25
+ }
26
+ function rewardBar(version) {
27
+ const filled = Math.round((version / TOTAL_VERSIONS) * REWARD_BAR_WIDTH);
28
+ return BAR_FULL.repeat(filled) + BAR_EMPTY.repeat(REWARD_BAR_WIDTH - filled);
29
+ }
17
30
  /**
18
- * Welcome animation frames - SynergySpec-SelfEvolving logo building from center
19
- * 7 rows × 6 columns diamond with hollow center "O"
20
- * Center bar is 2 cols × 3 rows (rows 3,4,5 cols 3,4)
21
- * Each frame is an array of strings (lines of ASCII art)
22
- * Grid: 6 cols × 2 chars = 12 chars wide
31
+ * One animation frame: the double-helix (constant height so the cursor-redraw
32
+ * invariant holds) plus a `policy vNN` header and a `reward ▓▓░░` footer that
33
+ * advance with the policy version. The twist phase shifts each version so the
34
+ * strands appear to rotate as the policy evolves.
23
35
  */
36
+ function helixFrame(version) {
37
+ // Two strands crossing over a fixed 7-char span; the cross point walks across
38
+ // the span by version so successive frames read as a rotating helix.
39
+ const span = 7;
40
+ const phase = (version - 1) % span;
41
+ const strandRow = (offset) => {
42
+ const a = (phase + offset) % span;
43
+ const b = (span - 1 - ((phase + offset) % span));
44
+ const cells = Array.from({ length: span }, () => ' ');
45
+ cells[a] = NODE;
46
+ cells[b] = cells[b] === NODE ? NODE : LINK;
47
+ if (a === b)
48
+ cells[a] = NODE;
49
+ return ` ${cells.join('')}`;
50
+ };
51
+ return [
52
+ ` policy v${pad2(version)}`,
53
+ '',
54
+ strandRow(0),
55
+ strandRow(1),
56
+ strandRow(2),
57
+ strandRow(3),
58
+ strandRow(4),
59
+ '',
60
+ ` reward ${rewardBar(version)}`,
61
+ ];
62
+ }
24
63
  export const WELCOME_ANIMATION = {
25
64
  interval: 120,
26
- frames: [
27
- // Frame 1: Empty
28
- [
29
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
30
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
31
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
32
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
33
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
34
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
35
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
36
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
37
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
38
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
39
- ],
40
- // Frame 2: Center blocks appear (dim) - 2x3 center bar
41
- [
42
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
43
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
44
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
45
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
46
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
47
- `${_}${_}${_}${_}${D}${D}${_}${_}`,
48
- `${_}${_}${_}${_}${D}${D}${_}${_}`,
49
- `${_}${_}${_}${_}${D}${D}${_}${_}`,
50
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
51
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
52
- ],
53
- // Frame 3: Center blocks solidify
54
- [
55
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
56
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
57
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
58
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
59
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
60
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
61
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
62
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
63
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
64
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
65
- ],
66
- // Frame 4: Top and bottom points appear
67
- [
68
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
69
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
70
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
71
- `${_}${_}${_}${_}${D}${D}${_}${_}`,
72
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
73
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
74
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
75
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
76
- `${_}${_}${_}${_}${_}${_}${_}${_}`,
77
- `${_}${_}${_}${_}${D}${D}${_}${_}`,
78
- ],
79
- // Frame 5: Inner ring forming
80
- [
81
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
82
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
83
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
84
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
85
- `${_}${_}${_}${D}${_}${_}${D}${_}`,
86
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
87
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
88
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
89
- `${_}${_}${_}${D}${_}${_}${D}${_}`,
90
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
91
- ],
92
- // Frame 6: Outer ring appearing
93
- [
94
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
95
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
96
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
97
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
98
- `${_}${_}${_}${F}${_}${_}${F}${_}`,
99
- `${_}${_}${D}${_}${F}${F}${_}${D}`,
100
- `${_}${_}${D}${_}${F}${F}${_}${D}`,
101
- `${_}${_}${D}${_}${F}${F}${_}${D}`,
102
- `${_}${_}${_}${F}${_}${_}${F}${_}`,
103
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
104
- ],
105
- // Frame 7: Full logo
106
- [
107
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
108
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
109
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
110
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
111
- `${_}${_}${_}${F}${_}${_}${F}${_}`,
112
- `${_}${_}${F}${_}${F}${F}${_}${F}`,
113
- `${_}${_}${F}${_}${F}${F}${_}${F}`,
114
- `${_}${_}${F}${_}${F}${F}${_}${F}`,
115
- `${_}${_}${_}${F}${_}${_}${F}${_}`,
116
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
117
- ],
118
- // Frame 8: Hold complete logo
119
- [
120
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 1
121
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 2
122
- `${_}${_}${_}${_}${_}${_}${_}${_}`, // padding row 3
123
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
124
- `${_}${_}${_}${F}${_}${_}${F}${_}`,
125
- `${_}${_}${F}${_}${F}${F}${_}${F}`,
126
- `${_}${_}${F}${_}${F}${F}${_}${F}`,
127
- `${_}${_}${F}${_}${F}${F}${_}${F}`,
128
- `${_}${_}${_}${F}${_}${_}${F}${_}`,
129
- `${_}${_}${_}${_}${F}${F}${_}${_}`,
130
- ],
131
- ],
65
+ frames: Array.from({ length: TOTAL_VERSIONS }, (_v, i) => helixFrame(i + 1)),
132
66
  };
133
67
  //# sourceMappingURL=ascii-patterns.js.map
@@ -2,6 +2,14 @@
2
2
  * Animated welcome screen for the experimental artifact workflow setup.
3
3
  * Shows side-by-side layout with animated ASCII art on left and welcome text on right.
4
4
  */
5
+ /**
6
+ * Welcome text content (right column)
7
+ */
8
+ export declare function getWelcomeText(): string[];
9
+ /**
10
+ * Renders a single frame with side-by-side layout
11
+ */
12
+ export declare function renderFrame(artLines: string[], textLines: string[]): string;
5
13
  /**
6
14
  * Shows the animated welcome screen.
7
15
  * Returns when user presses Enter.
@@ -11,7 +11,7 @@ const ART_COLUMN_WIDTH = 24;
11
11
  /**
12
12
  * Welcome text content (right column)
13
13
  */
14
- function getWelcomeText() {
14
+ export function getWelcomeText() {
15
15
  return [
16
16
  chalk.white.bold('Welcome to SynergySpec-SelfEvolving'),
17
17
  chalk.dim('A lightweight spec-driven framework'),
@@ -31,7 +31,7 @@ function getWelcomeText() {
31
31
  /**
32
32
  * Renders a single frame with side-by-side layout
33
33
  */
34
- function renderFrame(artLines, textLines) {
34
+ export function renderFrame(artLines, textLines) {
35
35
  const maxLines = Math.max(artLines.length, textLines.length);
36
36
  const lines = [];
37
37
  for (let i = 0; i < maxLines; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synergyspec-selfevolving",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "synergyspec-selfevolving",