synergyspec-selfevolving 2.1.5 → 2.1.6

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.
Files changed (37) hide show
  1. package/dist/commands/learn.js +80 -24
  2. package/dist/commands/self-evolution-dream.d.ts +15 -1
  3. package/dist/commands/self-evolution-dream.js +111 -6
  4. package/dist/commands/self-evolution-episode.d.ts +3 -0
  5. package/dist/commands/self-evolution-episode.js +157 -108
  6. package/dist/commands/workflow/status.js +4 -0
  7. package/dist/core/archive.js +17 -9
  8. package/dist/core/change-readiness.d.ts +16 -1
  9. package/dist/core/change-readiness.js +441 -15
  10. package/dist/core/fitness/loss.d.ts +3 -5
  11. package/dist/core/fitness/loss.js +2 -2
  12. package/dist/core/fitness/test-metrics.d.ts +1 -0
  13. package/dist/core/fitness/test-metrics.js +49 -0
  14. package/dist/core/learn.js +129 -11
  15. package/dist/core/migration.d.ts +6 -14
  16. package/dist/core/migration.js +63 -21
  17. package/dist/core/runner-evidence.d.ts +53 -0
  18. package/dist/core/runner-evidence.js +613 -0
  19. package/dist/core/self-evolution/candidates.js +0 -2
  20. package/dist/core/self-evolution/dream.d.ts +57 -3
  21. package/dist/core/self-evolution/dream.js +480 -9
  22. package/dist/core/self-evolution/episode-orchestrator.d.ts +2 -0
  23. package/dist/core/self-evolution/episode-orchestrator.js +17 -5
  24. package/dist/core/self-evolution/episode-store.d.ts +5 -0
  25. package/dist/core/self-evolution/episode-store.js +6 -2
  26. package/dist/core/self-evolution/evolving-agent.js +8 -0
  27. package/dist/core/self-evolution/host-harness.d.ts +35 -12
  28. package/dist/core/self-evolution/host-harness.js +188 -49
  29. package/dist/core/self-evolution/reward-aggregator.js +2 -2
  30. package/dist/core/templates/workflows/archive-change.js +18 -18
  31. package/dist/core/templates/workflows/dream.js +57 -47
  32. package/dist/core/templates/workflows/learn.js +7 -5
  33. package/dist/core/templates/workflows/run-tests.js +48 -29
  34. package/dist/core/templates/workflows/self-evolving.js +11 -8
  35. package/dist/core/trajectory/facts.d.ts +1 -1
  36. package/dist/core/trajectory/registry.js +39 -8
  37. package/package.json +1 -1
@@ -41,6 +41,32 @@ function allPassedReportSummary(line) {
41
41
  return null;
42
42
  return Number(passed[1]);
43
43
  }
44
+ function aggregateMetrics(items) {
45
+ const totals = items.reduce((acc, item) => ({
46
+ total: acc.total + item.total,
47
+ passed: acc.passed + item.passed,
48
+ failed: acc.failed + item.failed,
49
+ }), { total: 0, passed: 0, failed: 0 });
50
+ return {
51
+ ...totals,
52
+ passRate: rate(totals.passed, totals.total),
53
+ };
54
+ }
55
+ function synergySpecReportSummary(line) {
56
+ const summary = /^Summary:\s*\d+\s+collected,\s*(\d+)\s+passed,\s*(\d+)\s+failed(?:,\s*\d+\s+skipped)?(?:,\s*(\d+)\s+collection\s+errors?)?\.?$/i.exec(line);
57
+ if (!summary)
58
+ return null;
59
+ const passed = Number(summary[1]);
60
+ const failed = Number(summary[2]);
61
+ const collectionErrors = summary[3] !== undefined ? Number(summary[3]) : 0;
62
+ const total = passed + failed + collectionErrors;
63
+ return {
64
+ total,
65
+ passed,
66
+ failed: failed + collectionErrors,
67
+ passRate: rate(passed, total),
68
+ };
69
+ }
44
70
  /**
45
71
  * Extract pass/fail counts from test-runner summary text. Recognizes the
46
72
  * canonical runner summary shapes and takes the LAST one (runners print the
@@ -50,6 +76,7 @@ function allPassedReportSummary(line) {
50
76
  * "1 failed, 9 passed in 0.4s" (order-independent)
51
77
  * - SynergySpec reports: table/status prose like
52
78
  * "Passed; 85 tests collected and passed"
79
+ * "Summary: 29 collected, 29 passed, 0 failed, 0 skipped, 0 collection errors."
53
80
  * A bare "N passed" line that is not a recognized summary (e.g. prose or a
54
81
  * per-suite tally) is ignored. Pytest "errors" count toward `failed`.
55
82
  * Returns null when no recognized summary is found.
@@ -59,8 +86,18 @@ export function parseTestMetrics(reportText) {
59
86
  return null;
60
87
  const text = reportText.replace(ANSI_SGR, '');
61
88
  let result = null;
89
+ let authoritativeSummary = null;
90
+ const pytestSummaries = [];
91
+ let nonPytestSummarySeen = false;
62
92
  for (const raw of text.split(/\r?\n/)) {
63
93
  const line = raw.trim();
94
+ const synergySpecSummary = synergySpecReportSummary(line);
95
+ if (synergySpecSummary !== null) {
96
+ result = synergySpecSummary;
97
+ authoritativeSummary = synergySpecSummary;
98
+ nonPytestSummarySeen = true;
99
+ continue;
100
+ }
64
101
  const passed = count(line, /(\d+)\s+passed\b/i);
65
102
  if (passed === null) {
66
103
  const reportPassed = allPassedReportSummary(line);
@@ -71,6 +108,7 @@ export function parseTestMetrics(reportText) {
71
108
  failed: 0,
72
109
  passRate: rate(reportPassed, reportPassed),
73
110
  };
111
+ nonPytestSummarySeen = true;
74
112
  }
75
113
  continue;
76
114
  }
@@ -86,6 +124,17 @@ export function parseTestMetrics(reportText) {
86
124
  continue;
87
125
  const total = passed + failed + errored;
88
126
  result = { total, passed, failed: failed + errored, passRate: rate(passed, total) };
127
+ if (/\bin\s+[\d.]+\s*m?s\b/i.test(line)) {
128
+ pytestSummaries.push(result);
129
+ }
130
+ else {
131
+ nonPytestSummarySeen = true;
132
+ }
133
+ }
134
+ if (authoritativeSummary)
135
+ return authoritativeSummary;
136
+ if (pytestSummaries.length > 1 && !nonPytestSummarySeen) {
137
+ return aggregateMetrics(pytestSummaries);
89
138
  }
90
139
  return result;
91
140
  }
@@ -8,10 +8,11 @@ import { parseTasksMarkdown } from './self-evolution/task-quality.js';
8
8
  import { buildLLMSummaryCandidates, } from './learn/llm-summary.js';
9
9
  import { parseTestMetrics, computePerChangeLoss, measureHealthReport, resolveMetricSource, } from './fitness/index.js';
10
10
  import { readProjectConfig } from './project-config.js';
11
- import { getTrajectoryForChange } from './trajectory/registry.js';
12
- import { toTrajectoryFacts, extractExpectedTestPaths } from './trajectory/facts.js';
11
+ import { getTrajectoryResultForChange } from './trajectory/registry.js';
12
+ import { toTrajectoryFacts, extractExpectedTestPaths, } from './trajectory/facts.js';
13
13
  import { toActionSkeleton, renderActionSkeleton } from './trajectory/skeleton.js';
14
14
  import { walkCreditPath } from './learn/credit-path.js';
15
+ import { readRunnerEvidenceResultFromTestReport, runnerEvidenceToTrajectoryFacts, } from './runner-evidence.js';
15
16
  const PRIMARY_ARTIFACTS = [
16
17
  ['proposal', 'proposal.md'],
17
18
  ['usecases', 'usecases.md'],
@@ -51,7 +52,19 @@ export async function generateLearnReport(args = {}) {
51
52
  // that is absent or `stub` the source returns no signal, `measureHealthPenalty`
52
53
  // yields null, and the loss is byte-identical to the functional-only baseline.
53
54
  const testReport = artifacts.evidence.find((f) => /(?:^|[\\/])(?:test-report|run-tests?-report|ci-report)\.md$/i.test(f.relativePath));
54
- const testMetrics = testReport ? parseTestMetrics(testReport.content) : null;
55
+ const specTestsForScope = artifacts.evidence.find((f) => /(?:^|[\\/])spec-tests\.md$/i.test(f.relativePath));
56
+ const expectedTestPaths = extractExpectedTestPaths(specTestsForScope?.content);
57
+ const runnerEvidenceResult = testReport
58
+ ? await readRunnerEvidenceResultFromTestReport({
59
+ projectRoot,
60
+ changeName: resolved.changeName,
61
+ changeDir: resolved.changeDir,
62
+ testReportContent: testReport.content,
63
+ })
64
+ : { evidence: null };
65
+ const runnerEvidence = runnerEvidenceResult.evidence;
66
+ const reportTestMetrics = testReport ? parseTestMetrics(testReport.content) : null;
67
+ const testMetrics = runnerEvidence?.testMetrics ?? reportTestMetrics;
55
68
  let healthPenalty;
56
69
  let healthSourceName;
57
70
  let healthContributors = [];
@@ -71,23 +84,71 @@ export async function generateLearnReport(args = {}) {
71
84
  // than trusting the hand-authored test-report. `getTrajectoryForChange`
72
85
  // returns null when nothing is discoverable, so on the artifact-only path
73
86
  // `trajectoryFacts` is null and the fitness output is byte-identical baseline.
74
- const trajectory = args.trajectorySource
75
- ? await args.trajectorySource.getTrajectory(resolved.changeName).catch(() => null)
76
- : await getTrajectoryForChange(projectRoot, resolved.changeName);
87
+ let trajectoryMiss = null;
88
+ let trajectory;
89
+ if (args.trajectorySource) {
90
+ try {
91
+ if (args.trajectorySource.getTrajectoryResult) {
92
+ const trajectoryResult = await args.trajectorySource.getTrajectoryResult(resolved.changeName);
93
+ trajectory = trajectoryResult.trajectory;
94
+ if (!trajectory) {
95
+ trajectoryMiss = {
96
+ reason: trajectoryResult.reason ?? 'adapter-returned-null',
97
+ sourceHarness: args.trajectorySource.harness ?? null,
98
+ };
99
+ }
100
+ }
101
+ else {
102
+ trajectory = await args.trajectorySource.getTrajectory(resolved.changeName);
103
+ }
104
+ }
105
+ catch (err) {
106
+ trajectory = null;
107
+ trajectoryMiss = {
108
+ reason: err instanceof Error ? err.message : String(err),
109
+ sourceHarness: args.trajectorySource.harness ?? null,
110
+ };
111
+ }
112
+ }
113
+ else {
114
+ const trajectoryResult = await getTrajectoryResultForChange(projectRoot, resolved.changeName);
115
+ trajectory = trajectoryResult.trajectory;
116
+ if (!trajectory) {
117
+ trajectoryMiss = {
118
+ reason: trajectoryResult.reason ?? 'adapter-returned-null',
119
+ sourceHarness: trajectoryResult.sourceHarness,
120
+ };
121
+ }
122
+ }
77
123
  // Change-scope guard input: the change's own expected test paths (from its
78
124
  // spec-tests.md mapping). Lets toTrajectoryFacts DEMOTE a green-but-irrelevant
79
125
  // run (a default `pytest` that collected ZERO of the change's tests) to
80
126
  // unverified so the 奖励智能体 REWARD AGENT abstains instead of certifying a
81
127
  // false-GREEN. Absent/empty ⇒ no gate (byte-identical baseline).
82
- const specTestsForScope = artifacts.evidence.find((f) => /(?:^|[\\/])spec-tests\.md$/i.test(f.relativePath));
83
- const expectedTestPaths = extractExpectedTestPaths(specTestsForScope?.content);
84
- const trajectoryFacts = toTrajectoryFacts(trajectory, resolved.changeName, {
128
+ const rawTrajectoryFacts = toTrajectoryFacts(trajectory, resolved.changeName, {
85
129
  expectedTestPaths,
86
130
  });
131
+ const runnerEvidenceFacts = runnerEvidence
132
+ ? runnerEvidenceToTrajectoryFacts({
133
+ evidence: runnerEvidence,
134
+ changeName: resolved.changeName,
135
+ expectedTestPaths,
136
+ })
137
+ : null;
138
+ const runnerTrajectoryConflict = runnerEvidenceFacts?.verified === true &&
139
+ rawTrajectoryFacts?.verified === true &&
140
+ factsDisagree(runnerEvidenceFacts, rawTrajectoryFacts);
141
+ const trajectoryFacts = runnerEvidenceFacts?.verified === true
142
+ ? runnerEvidenceFacts
143
+ : (!rawTrajectoryFacts ||
144
+ !rawTrajectoryFacts.testRunObserved ||
145
+ (rawTrajectoryFacts.observedStatus === null && rawTrajectoryFacts.observedPassRate === null)) &&
146
+ runnerEvidenceFacts
147
+ ? runnerEvidenceFacts
148
+ : rawTrajectoryFacts;
87
149
  // "Trust the trajectory": when a real runner was observed, its pass rate wins
88
150
  // over the authored test-report; otherwise the report stands but is flagged
89
- // unverified (observe-only soft penalty `unverifiedWeight` defaults to 0, so
90
- // the loss is unchanged until a maintainer raises it).
151
+ // unverified and receives the default soft penalty.
91
152
  const observedPassRate = trajectoryFacts?.testRunObserved && trajectoryFacts.observedPassRate !== null
92
153
  ? trajectoryFacts.observedPassRate
93
154
  : null;
@@ -217,6 +278,54 @@ export async function generateLearnReport(args = {}) {
217
278
  });
218
279
  }
219
280
  }
281
+ if (runnerTrajectoryConflict && runnerEvidenceFacts && rawTrajectoryFacts) {
282
+ observations.push({
283
+ code: 'runner-trajectory-conflict',
284
+ summary: 'Durable runner-exit evidence disagrees with the host trajectory; runner-exit.json is the graded functional source.',
285
+ evidence: [
286
+ ...runnerEvidenceFacts.sourcePaths.slice(0, 2).map((file) => ({
287
+ file,
288
+ detail: `runner evidence status=${runnerEvidenceFacts.observedStatus ?? 'unknown'} passRate=${runnerEvidenceFacts.observedPassRate ?? 'n/a'}`,
289
+ })),
290
+ ...rawTrajectoryFacts.sourcePaths.slice(0, 2).map((file) => ({
291
+ file,
292
+ detail: `trajectory status=${rawTrajectoryFacts.observedStatus ?? 'unknown'} passRate=${rawTrajectoryFacts.observedPassRate ?? 'n/a'}`,
293
+ })),
294
+ ],
295
+ tags: ['runner-evidence', 'trajectory', 'conflict', 'fitness'],
296
+ severity: 'action',
297
+ });
298
+ }
299
+ if (!rawTrajectoryFacts && trajectoryMiss !== null) {
300
+ observations.push({
301
+ code: 'trajectory-source-miss',
302
+ summary: `No observed host trajectory was available (${trajectoryMiss.reason}).`,
303
+ evidence: [
304
+ {
305
+ file: relativePath(projectRoot, resolved.changeDir),
306
+ detail: `sourceHarness=${trajectoryMiss.sourceHarness ?? 'unresolved'}; reason=${trajectoryMiss.reason}`,
307
+ },
308
+ ],
309
+ tags: ['trajectory', 'unverified', 'fitness'],
310
+ severity: runnerEvidenceFacts?.verified === true ? 'info' : 'action',
311
+ });
312
+ }
313
+ if (runnerEvidenceResult.reason &&
314
+ runnerEvidenceResult.exitJsonPath &&
315
+ runnerEvidenceFacts?.verified !== true) {
316
+ observations.push({
317
+ code: 'runner-evidence-miss',
318
+ summary: `Runner evidence was present but could not be used (${runnerEvidenceResult.reason}).`,
319
+ evidence: [
320
+ {
321
+ file: relativePath(projectRoot, runnerEvidenceResult.exitJsonPath),
322
+ detail: runnerEvidenceResult.reason,
323
+ },
324
+ ],
325
+ tags: ['runner-evidence', 'unverified', 'fitness'],
326
+ severity: trajectoryFacts?.verified === true ? 'info' : 'action',
327
+ });
328
+ }
220
329
  // Health head is CONFIGURED (a non-stub source was selected) but produced NO
221
330
  // signal: surface it loudly rather than letting the health half of the loss
222
331
  // silently default to 0. Default-on health must never fail invisibly — this
@@ -335,6 +444,15 @@ function buildLearnObservations(report) {
335
444
  }
336
445
  return observations;
337
446
  }
447
+ function factsDisagree(left, right) {
448
+ if (left.observedStatus !== right.observedStatus)
449
+ return true;
450
+ const leftRate = left.observedPassRate;
451
+ const rightRate = right.observedPassRate;
452
+ if (leftRate === null || rightRate === null)
453
+ return leftRate !== rightRate;
454
+ return Math.abs(leftRate - rightRate) > 0.000001;
455
+ }
338
456
  export async function applyLearnCandidates(args) {
339
457
  return applyLearnMemoryCandidates({
340
458
  projectRoot: args.projectRoot,
@@ -11,22 +11,14 @@ import type { AIToolOption } from './config.js';
11
11
  */
12
12
  export declare function scanInstalledWorkflows(projectPath: string, tools: AIToolOption[]): string[];
13
13
  /**
14
- * No-op: a custom profile's workflow list is the user's deliberate choice.
14
+ * One-time compatibility upgrade for v2.1.5.
15
15
  *
16
- * Previously this function appended every entry from ALL_WORKFLOWS to the
17
- * user's custom workflow list so that newly-shipped workflows would
18
- * auto-enable. That defeats the contract "custom means custom". A user
19
- * who wants every workflow picks `profile: 'all'`; a user on `profile:
20
- * 'custom'` is intentionally limiting their setup, and auto-appending
21
- * silently invalidates that choice.
16
+ * Most custom profiles are deliberate and must stay untouched. The exception
17
+ * is an exact snapshot of every built-in workflow from before Dream existed:
18
+ * that snapshot behaves like "all workflows" and should receive Dream once.
22
19
  *
23
- * Users who want to enable an additional workflow after a `synergyspec-selfevolving`
24
- * upgrade can do so explicitly via `synergyspec-selfevolving config`. Fix:
25
- * `fix-custom-profile-filtering` (2026-05-17).
26
- *
27
- * The function is kept (rather than deleted) so existing callers in
28
- * `init.ts` and `update.ts` continue to compile without coordinated
29
- * edits. A future cleanup change can remove all three at once.
20
+ * A migration marker prevents re-adding Dream if the user removes it after this
21
+ * backfill has already run.
30
22
  */
31
23
  export declare function upgradeCustomWorkflows(): void;
32
24
  /**
@@ -10,6 +10,26 @@ import { WORKFLOW_TO_SKILL_DIR } from './profile-sync-drift.js';
10
10
  import { ALL_WORKFLOWS } from './profiles.js';
11
11
  import path from 'path';
12
12
  import * as fs from 'fs';
13
+ const DREAM_WORKFLOW_BACKFILL_MARKER = 'dream-workflow-v2.1.5';
14
+ const LEGACY_ALL_WORKFLOWS_BEFORE_DREAM = [
15
+ 'propose',
16
+ 'explore',
17
+ 'new',
18
+ 'continue',
19
+ 'apply',
20
+ 'tdd',
21
+ 'ff',
22
+ 'sync',
23
+ 'archive',
24
+ 'bulk-archive',
25
+ 'verify',
26
+ 'learn',
27
+ 'verify-spec',
28
+ 'onboard',
29
+ 'gen-tests',
30
+ 'run-tests',
31
+ 'ci',
32
+ ];
13
33
  function scanInstalledWorkflowArtifacts(projectPath, tools) {
14
34
  const installed = new Set();
15
35
  let hasSkills = false;
@@ -62,26 +82,51 @@ function inferDelivery(artifacts) {
62
82
  }
63
83
  return 'skills';
64
84
  }
85
+ function asStringArray(value) {
86
+ return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
87
+ }
88
+ function hasSameWorkflowSet(actual, expected) {
89
+ if (actual.length !== expected.length)
90
+ return false;
91
+ const actualSet = new Set(actual);
92
+ return expected.every((workflow) => actualSet.has(workflow));
93
+ }
94
+ function hasMigrationMarker(config, marker) {
95
+ return asStringArray(config.migrations).includes(marker);
96
+ }
97
+ function appendMigrationMarker(config, marker) {
98
+ const migrations = asStringArray(config.migrations);
99
+ if (!migrations.includes(marker)) {
100
+ config.migrations = [...migrations, marker];
101
+ }
102
+ }
65
103
  /**
66
- * No-op: a custom profile's workflow list is the user's deliberate choice.
67
- *
68
- * Previously this function appended every entry from ALL_WORKFLOWS to the
69
- * user's custom workflow list so that newly-shipped workflows would
70
- * auto-enable. That defeats the contract — "custom means custom". A user
71
- * who wants every workflow picks `profile: 'all'`; a user on `profile:
72
- * 'custom'` is intentionally limiting their setup, and auto-appending
73
- * silently invalidates that choice.
104
+ * One-time compatibility upgrade for v2.1.5.
74
105
  *
75
- * Users who want to enable an additional workflow after a `synergyspec-selfevolving`
76
- * upgrade can do so explicitly via `synergyspec-selfevolving config`. Fix:
77
- * `fix-custom-profile-filtering` (2026-05-17).
106
+ * Most custom profiles are deliberate and must stay untouched. The exception
107
+ * is an exact snapshot of every built-in workflow from before Dream existed:
108
+ * that snapshot behaves like "all workflows" and should receive Dream once.
78
109
  *
79
- * The function is kept (rather than deleted) so existing callers in
80
- * `init.ts` and `update.ts` continue to compile without coordinated
81
- * edits. A future cleanup change can remove all three at once.
110
+ * A migration marker prevents re-adding Dream if the user removes it after this
111
+ * backfill has already run.
82
112
  */
83
113
  export function upgradeCustomWorkflows() {
84
- // intentionally empty — see comment above.
114
+ const config = getGlobalConfig();
115
+ if (config.profile !== 'custom')
116
+ return;
117
+ if (hasMigrationMarker(config, DREAM_WORKFLOW_BACKFILL_MARKER))
118
+ return;
119
+ const workflows = asStringArray(config.workflows);
120
+ if (workflows.includes('dream')) {
121
+ appendMigrationMarker(config, DREAM_WORKFLOW_BACKFILL_MARKER);
122
+ saveGlobalConfig(config);
123
+ return;
124
+ }
125
+ if (!hasSameWorkflowSet(workflows, LEGACY_ALL_WORKFLOWS_BEFORE_DREAM))
126
+ return;
127
+ config.workflows = [...LEGACY_ALL_WORKFLOWS_BEFORE_DREAM, 'dream'];
128
+ appendMigrationMarker(config, DREAM_WORKFLOW_BACKFILL_MARKER);
129
+ saveGlobalConfig(config);
85
130
  }
86
131
  /**
87
132
  * Performs one-time migration if the global config does not yet have a profile field.
@@ -94,7 +139,7 @@ export function upgradeCustomWorkflows() {
94
139
  */
95
140
  export function migrateIfNeeded(projectPath, tools) {
96
141
  const config = getGlobalConfig();
97
- // Check raw config file for profile field presence
142
+ // Check raw config file for profile field presence.
98
143
  const configPath = getGlobalConfigPath();
99
144
  let rawConfig = {};
100
145
  try {
@@ -103,20 +148,17 @@ export function migrateIfNeeded(projectPath, tools) {
103
148
  }
104
149
  }
105
150
  catch {
106
- return; // Can't read config, skip migration
151
+ return;
107
152
  }
108
- // If profile is already explicitly set, no migration needed
153
+ // If profile is already explicitly set, no migration needed.
109
154
  if (rawConfig.profile !== undefined) {
110
155
  return;
111
156
  }
112
- // Scan for installed workflows
113
157
  const artifacts = scanInstalledWorkflowArtifacts(projectPath, tools);
114
158
  const installedWorkflows = artifacts.workflows;
115
159
  if (installedWorkflows.length === 0) {
116
- // No workflows installed, new user — defaults will apply
117
160
  return;
118
161
  }
119
- // Migrate: set profile to custom with detected workflows
120
162
  config.profile = 'custom';
121
163
  config.workflows = installedWorkflows;
122
164
  if (rawConfig.delivery === undefined) {
@@ -0,0 +1,53 @@
1
+ import { type TestMetrics } from './fitness/test-metrics.js';
2
+ import type { TrajectoryFacts } from './trajectory/facts.js';
3
+ export interface WorkspaceIdentityFileSnapshot {
4
+ path?: string;
5
+ name?: string;
6
+ sha256?: string;
7
+ }
8
+ export interface WorkspaceIdentitySnapshot {
9
+ cwd?: string;
10
+ changeName?: string;
11
+ taskId?: string;
12
+ pyproject?: WorkspaceIdentityFileSnapshot;
13
+ packageJson?: WorkspaceIdentityFileSnapshot;
14
+ }
15
+ export interface RunnerEvidence {
16
+ exitJsonPath: string;
17
+ command?: string;
18
+ stdoutLogPath?: string;
19
+ stderrLogPath?: string;
20
+ exitCode: number | null;
21
+ testMetrics: TestMetrics | null;
22
+ outputText: string;
23
+ sourcePaths: string[];
24
+ workspaceIdentity: {
25
+ verified: boolean;
26
+ reason?: string;
27
+ recorded?: WorkspaceIdentitySnapshot;
28
+ current: WorkspaceIdentitySnapshot;
29
+ };
30
+ }
31
+ export interface RunnerEvidenceLookupResult {
32
+ evidence: RunnerEvidence | null;
33
+ reason?: string;
34
+ exitJsonPath?: string;
35
+ }
36
+ export declare function readRunnerEvidenceFromTestReport(args: {
37
+ projectRoot: string;
38
+ changeName: string;
39
+ changeDir?: string;
40
+ testReportContent: string;
41
+ }): Promise<RunnerEvidence | null>;
42
+ export declare function readRunnerEvidenceResultFromTestReport(args: {
43
+ projectRoot: string;
44
+ changeName: string;
45
+ changeDir?: string;
46
+ testReportContent: string;
47
+ }): Promise<RunnerEvidenceLookupResult>;
48
+ export declare function runnerEvidenceToTrajectoryFacts(args: {
49
+ evidence: RunnerEvidence;
50
+ changeName: string;
51
+ expectedTestPaths?: string[];
52
+ }): TrajectoryFacts | null;
53
+ //# sourceMappingURL=runner-evidence.d.ts.map