@haibun/core 3.0.3 → 3.1.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/build/currentVersion.d.ts +1 -1
- package/build/currentVersion.js +1 -2
- package/build/lib/EventLogger.d.ts +22 -0
- package/build/lib/EventLogger.d.ts.map +1 -0
- package/build/lib/EventLogger.js +95 -0
- package/build/lib/EventLogger.js.map +1 -0
- package/build/lib/Logger.d.ts.map +1 -1
- package/build/lib/Logger.js +2 -1
- package/build/lib/Logger.js.map +1 -1
- package/build/lib/astepper.d.ts +11 -1
- package/build/lib/astepper.d.ts.map +1 -1
- package/build/lib/astepper.js +8 -0
- package/build/lib/astepper.js.map +1 -1
- package/build/lib/core/flow-runner.d.ts +25 -0
- package/build/lib/core/flow-runner.d.ts.map +1 -0
- package/build/lib/core/flow-runner.js +128 -0
- package/build/lib/core/flow-runner.js.map +1 -0
- package/build/lib/core/protocol.d.ts +58 -0
- package/build/lib/core/protocol.d.ts.map +1 -0
- package/build/lib/core/protocol.js +18 -0
- package/build/lib/core/protocol.js.map +1 -0
- package/build/lib/core-domains.d.ts +2 -16
- package/build/lib/core-domains.d.ts.map +1 -1
- package/build/lib/core-domains.js +46 -27
- package/build/lib/core-domains.js.map +1 -1
- package/build/lib/defs.d.ts +42 -11
- package/build/lib/defs.d.ts.map +1 -1
- package/build/lib/defs.js +6 -1
- package/build/lib/defs.js.map +1 -1
- package/build/lib/domain-types.d.ts +12 -1
- package/build/lib/domain-types.d.ts.map +1 -1
- package/build/lib/domain-types.js +63 -3
- package/build/lib/domain-types.js.map +1 -1
- package/build/lib/event-bridge.d.ts +28 -0
- package/build/lib/event-bridge.d.ts.map +1 -0
- package/build/lib/event-bridge.js +114 -0
- package/build/lib/event-bridge.js.map +1 -0
- package/build/lib/feature-variables.d.ts +14 -0
- package/build/lib/feature-variables.d.ts.map +1 -1
- package/build/lib/feature-variables.js +128 -6
- package/build/lib/feature-variables.js.map +1 -1
- package/build/lib/interfaces/logger.d.ts +19 -3
- package/build/lib/interfaces/logger.d.ts.map +1 -1
- package/build/lib/interfaces/logger.js +11 -1
- package/build/lib/interfaces/logger.js.map +1 -1
- package/build/lib/namedVars.d.ts.map +1 -1
- package/build/lib/namedVars.js +18 -10
- package/build/lib/namedVars.js.map +1 -1
- package/build/lib/populateActionArgs.d.ts +1 -1
- package/build/lib/populateActionArgs.d.ts.map +1 -1
- package/build/lib/populateActionArgs.js +11 -49
- package/build/lib/populateActionArgs.js.map +1 -1
- package/build/lib/test/TestSteps.d.ts +1 -0
- package/build/lib/test/TestSteps.d.ts.map +1 -1
- package/build/lib/test/TestStepsWithOptions.d.ts +1 -0
- package/build/lib/test/TestStepsWithOptions.d.ts.map +1 -1
- package/build/lib/test/lib.d.ts.map +1 -1
- package/build/lib/test/lib.js +2 -0
- package/build/lib/test/lib.js.map +1 -1
- package/build/lib/util/actualURI.js +1 -1
- package/build/lib/util/actualURI.js.map +1 -1
- package/build/lib/util/index.d.ts +11 -2
- package/build/lib/util/index.d.ts.map +1 -1
- package/build/lib/util/index.js +46 -2
- package/build/lib/util/index.js.map +1 -1
- package/build/lib/util/variables.d.ts +9 -0
- package/build/lib/util/variables.d.ts.map +1 -0
- package/build/lib/util/variables.js +40 -0
- package/build/lib/util/variables.js.map +1 -0
- package/build/lib/util/workspace-lib.d.ts.map +1 -1
- package/build/lib/util/workspace-lib.js +30 -3
- package/build/lib/util/workspace-lib.js.map +1 -1
- package/build/monitor/browser-stubs.d.ts +12 -0
- package/build/monitor/browser-stubs.d.ts.map +1 -0
- package/build/monitor/browser-stubs.js +20 -0
- package/build/monitor/browser-stubs.js.map +1 -0
- package/build/monitor/constants.d.ts +9 -0
- package/build/monitor/constants.d.ts.map +1 -0
- package/build/monitor/constants.js +9 -0
- package/build/monitor/constants.js.map +1 -0
- package/build/monitor/event-view.d.ts +40 -0
- package/build/monitor/event-view.d.ts.map +1 -0
- package/build/monitor/event-view.js +104 -0
- package/build/monitor/event-view.js.map +1 -0
- package/build/monitor/filters.d.ts +17 -0
- package/build/monitor/filters.d.ts.map +1 -0
- package/build/monitor/filters.js +32 -0
- package/build/monitor/filters.js.map +1 -0
- package/build/monitor/formatters.d.ts +27 -0
- package/build/monitor/formatters.d.ts.map +1 -0
- package/build/monitor/formatters.js +89 -0
- package/build/monitor/formatters.js.map +1 -0
- package/build/monitor/index.d.ts +11 -0
- package/build/monitor/index.d.ts.map +1 -0
- package/build/monitor/index.js +15 -0
- package/build/monitor/index.js.map +1 -0
- package/build/monitor/jit-serialization.d.ts +9 -0
- package/build/monitor/jit-serialization.d.ts.map +1 -0
- package/build/monitor/jit-serialization.js +73 -0
- package/build/monitor/jit-serialization.js.map +1 -0
- package/build/monitor/monitor-types.d.ts +119 -0
- package/build/monitor/monitor-types.d.ts.map +1 -0
- package/build/monitor/monitor-types.js +43 -0
- package/build/monitor/monitor-types.js.map +1 -0
- package/build/monitor/speculative-tracker.d.ts +20 -0
- package/build/monitor/speculative-tracker.d.ts.map +1 -0
- package/build/monitor/speculative-tracker.js +38 -0
- package/build/monitor/speculative-tracker.js.map +1 -0
- package/build/monitor/state.d.ts +31 -0
- package/build/monitor/state.d.ts.map +1 -0
- package/build/monitor/state.js +110 -0
- package/build/monitor/state.js.map +1 -0
- package/build/monitor/timer.d.ts +11 -0
- package/build/monitor/timer.d.ts.map +1 -0
- package/build/monitor/timer.js +13 -0
- package/build/monitor/timer.js.map +1 -0
- package/build/monitor/tree-builder.d.ts +25 -0
- package/build/monitor/tree-builder.d.ts.map +1 -0
- package/build/monitor/tree-builder.js +36 -0
- package/build/monitor/tree-builder.js.map +1 -0
- package/build/phases/Executor.d.ts.map +1 -1
- package/build/phases/Executor.js +102 -43
- package/build/phases/Executor.js.map +1 -1
- package/build/phases/Resolver.d.ts +2 -1
- package/build/phases/Resolver.d.ts.map +1 -1
- package/build/phases/Resolver.js +64 -1
- package/build/phases/Resolver.js.map +1 -1
- package/build/runner.d.ts.map +1 -1
- package/build/runner.js +5 -0
- package/build/runner.js.map +1 -1
- package/build/schema/events.d.ts +174 -0
- package/build/schema/events.d.ts.map +1 -0
- package/build/schema/events.js +55 -0
- package/build/schema/events.js.map +1 -0
- package/build/steps/activities-stepper.d.ts +55 -20
- package/build/steps/activities-stepper.d.ts.map +1 -1
- package/build/steps/activities-stepper.js +342 -207
- package/build/steps/activities-stepper.js.map +1 -1
- package/build/steps/conformance.d.ts +1 -0
- package/build/steps/conformance.d.ts.map +1 -1
- package/build/steps/console-monitor-stepper.d.ts +44 -0
- package/build/steps/console-monitor-stepper.d.ts.map +1 -0
- package/build/steps/console-monitor-stepper.js +106 -0
- package/build/steps/console-monitor-stepper.js.map +1 -0
- package/build/steps/debugger-stepper.d.ts +5 -3
- package/build/steps/debugger-stepper.d.ts.map +1 -1
- package/build/steps/debugger-stepper.js +35 -11
- package/build/steps/debugger-stepper.js.map +1 -1
- package/build/steps/haibun.d.ts +11 -18
- package/build/steps/haibun.d.ts.map +1 -1
- package/build/steps/haibun.js +51 -55
- package/build/steps/haibun.js.map +1 -1
- package/build/steps/lib/tts.d.ts.map +1 -1
- package/build/steps/lib/tts.js +2 -1
- package/build/steps/lib/tts.js.map +1 -1
- package/build/steps/logic-stepper.d.ts +42 -0
- package/build/steps/logic-stepper.d.ts.map +1 -0
- package/build/steps/logic-stepper.js +143 -0
- package/build/steps/logic-stepper.js.map +1 -0
- package/build/steps/narrator.d.ts.map +1 -1
- package/build/steps/narrator.js +1 -1
- package/build/steps/narrator.js.map +1 -1
- package/build/steps/parse.d.ts +1 -0
- package/build/steps/parse.d.ts.map +1 -1
- package/build/steps/variables-stepper.d.ts +11 -71
- package/build/steps/variables-stepper.d.ts.map +1 -1
- package/build/steps/variables-stepper.js +534 -84
- package/build/steps/variables-stepper.js.map +1 -1
- package/package.json +16 -3
- package/build/applyEffectFeatures.d.ts +0 -4
- package/build/applyEffectFeatures.d.ts.map +0 -1
- package/build/applyEffectFeatures.js +0 -31
- package/build/applyEffectFeatures.js.map +0 -1
- package/build/jsprolog/converter.d.ts +0 -9
- package/build/jsprolog/converter.d.ts.map +0 -1
- package/build/jsprolog/converter.js +0 -42
- package/build/jsprolog/converter.js.map +0 -1
- package/build/jsprolog/stepper-utils.d.ts +0 -8
- package/build/jsprolog/stepper-utils.d.ts.map +0 -1
- package/build/jsprolog/stepper-utils.js +0 -8
- package/build/jsprolog/stepper-utils.js.map +0 -1
- package/build/jsprolog/test.jsprolog.d.ts +0 -4
- package/build/jsprolog/test.jsprolog.d.ts.map +0 -1
- package/build/jsprolog/test.jsprolog.js +0 -19
- package/build/jsprolog/test.jsprolog.js.map +0 -1
- package/build/jsprolog/test.kireji.d.ts +0 -4
- package/build/jsprolog/test.kireji.d.ts.map +0 -1
- package/build/jsprolog/test.kireji.js +0 -19
- package/build/jsprolog/test.kireji.js.map +0 -1
- package/build/jsprolog/withAction.d.ts +0 -32
- package/build/jsprolog/withAction.d.ts.map +0 -1
- package/build/jsprolog/withAction.js +0 -65
- package/build/jsprolog/withAction.js.map +0 -1
- package/build/kireji/test.kireji.d.ts +0 -4
- package/build/kireji/test.kireji.d.ts.map +0 -1
- package/build/kireji/test.kireji.js +0 -19
- package/build/kireji/test.kireji.js.map +0 -1
- package/build/lib/errors.d.ts +0 -13
- package/build/lib/errors.d.ts.map +0 -1
- package/build/lib/errors.js +0 -18
- package/build/lib/errors.js.map +0 -1
- package/build/lib/util/featureStep-executor.d.ts +0 -7
- package/build/lib/util/featureStep-executor.d.ts.map +0 -1
- package/build/lib/util/featureStep-executor.js +0 -63
- package/build/lib/util/featureStep-executor.js.map +0 -1
- package/build/steps/conditions-stepper.d.ts +0 -27
- package/build/steps/conditions-stepper.d.ts.map +0 -1
- package/build/steps/conditions-stepper.js +0 -99
- package/build/steps/conditions-stepper.js.map +0 -1
|
@@ -1,131 +1,234 @@
|
|
|
1
1
|
import { AStepper } from '../lib/astepper.js';
|
|
2
|
-
import { OK } from '../lib/defs.js';
|
|
3
|
-
import { executeSubFeatureSteps, findFeatureStepsFromStatement } from '../lib/util/featureStep-executor.js';
|
|
4
|
-
import { ExecMode } from '../lib/defs.js';
|
|
2
|
+
import { OK, CycleWhen } from '../lib/defs.js';
|
|
5
3
|
import { actionOK, actionNotOK, getActionable } from '../lib/util/index.js';
|
|
6
4
|
import { DOMAIN_STATEMENT } from '../lib/domain-types.js';
|
|
7
5
|
import { EExecutionMessageType } from '../lib/interfaces/logger.js';
|
|
6
|
+
import { FlowRunner } from '../lib/core/flow-runner.js';
|
|
8
7
|
/**
|
|
9
8
|
* Stepper that dynamically builds virtual steps from `waypoint` statements.
|
|
10
9
|
* implements this logic: P ∨ (¬P ∧ [A]P)
|
|
11
10
|
*/
|
|
12
11
|
export class ActivitiesStepper extends AStepper {
|
|
13
|
-
|
|
14
|
-
// Track which outcome patterns were defined in backgrounds vs features
|
|
12
|
+
runner;
|
|
15
13
|
backgroundOutcomePatterns = new Set();
|
|
16
14
|
featureOutcomePatterns = new Set();
|
|
17
|
-
// Track which feature each feature-scoped outcome belongs to (for proper cleanup)
|
|
18
15
|
outcomeToFeaturePath = new Map();
|
|
19
|
-
|
|
16
|
+
featureSteps = new Map();
|
|
20
17
|
currentFeaturePath = '';
|
|
21
|
-
// Track the last feature path to clear outcomes from it when starting a new feature
|
|
22
18
|
lastFeaturePath = '';
|
|
23
|
-
|
|
19
|
+
lastResolutionPath = '';
|
|
24
20
|
ensuredInstances = new Map();
|
|
21
|
+
ensureAttempts = new Map();
|
|
22
|
+
registeredOutcomeMetadata = new Map();
|
|
23
|
+
inActivityBlock = false;
|
|
24
|
+
cycles = {
|
|
25
|
+
startExecution: () => {
|
|
26
|
+
this.sendGraphLinkMessages();
|
|
27
|
+
},
|
|
28
|
+
startFeature: (startFeature) => {
|
|
29
|
+
this.getWorld().logger.debug(`ActivitiesStepper.startFeature: starting feature at path "${startFeature.resolvedFeature.path}"`);
|
|
30
|
+
if (this.lastFeaturePath && this.lastFeaturePath !== startFeature.resolvedFeature.path) {
|
|
31
|
+
this.getWorld().logger.debug(`ActivitiesStepper.startFeature: clearing outcomes from previous feature "${this.lastFeaturePath}"`);
|
|
32
|
+
const previousSteps = this.featureSteps.get(this.lastFeaturePath);
|
|
33
|
+
if (previousSteps) {
|
|
34
|
+
for (const outcome of Object.keys(previousSteps)) {
|
|
35
|
+
delete this.steps[outcome];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const currentSteps = this.featureSteps.get(startFeature.resolvedFeature.path);
|
|
40
|
+
if (currentSteps) {
|
|
41
|
+
for (const [outcome, step] of Object.entries(currentSteps)) {
|
|
42
|
+
this.steps[outcome] = step;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
this.currentFeaturePath = startFeature.resolvedFeature.path;
|
|
46
|
+
this.sendGraphLinkMessages();
|
|
47
|
+
},
|
|
48
|
+
endFeature: () => {
|
|
49
|
+
this.lastFeaturePath = this.currentFeaturePath;
|
|
50
|
+
this.ensuredInstances.clear();
|
|
51
|
+
return Promise.resolve();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
cyclesWhen = {
|
|
55
|
+
startExecution: CycleWhen.FIRST,
|
|
56
|
+
startFeature: CycleWhen.FIRST,
|
|
57
|
+
};
|
|
25
58
|
baseSteps = {
|
|
26
59
|
activity: {
|
|
27
60
|
gwta: 'Activity: {activity}',
|
|
28
|
-
action: () => OK
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const activitiesStepper = stepper;
|
|
38
|
-
// Extract outcome name
|
|
39
|
-
const match = line.match(/^waypoint\s+(.+?)\s+with\s+(.+?)$/i);
|
|
40
|
-
if (!match) {
|
|
41
|
-
return false;
|
|
61
|
+
action: () => OK,
|
|
62
|
+
resolveFeatureLine: (line, path, _stepper, _backgrounds, allLines, lineIndex) => {
|
|
63
|
+
if (this.lastResolutionPath && this.lastResolutionPath !== path) {
|
|
64
|
+
const previousSteps = this.featureSteps.get(this.lastResolutionPath);
|
|
65
|
+
if (previousSteps) {
|
|
66
|
+
for (const outcome of Object.keys(previousSteps)) {
|
|
67
|
+
delete this.steps[outcome];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
42
70
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return true;
|
|
71
|
+
this.lastResolutionPath = path;
|
|
72
|
+
if (line.match(/^Activity:/i)) {
|
|
73
|
+
this.inActivityBlock = true;
|
|
74
|
+
return true; // Skip the Activity definition line itself
|
|
48
75
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
let activityBlockSteps;
|
|
55
|
-
if (allLines && lineIndex !== undefined) {
|
|
56
|
-
// Scan backwards from current line to find the Activity: marker
|
|
57
|
-
let activityStartLine = -1;
|
|
58
|
-
for (let i = lineIndex - 1; i >= 0; i--) {
|
|
59
|
-
const prevLine = getActionable(allLines[i]);
|
|
60
|
-
if (prevLine.match(/^Activity:/i)) {
|
|
61
|
-
activityStartLine = i;
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
// Stop if we hit another major section marker
|
|
65
|
-
if (prevLine.match(/^(Feature|Scenario|Background):/i)) {
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
76
|
+
if (this.inActivityBlock) {
|
|
77
|
+
// Check for block terminators
|
|
78
|
+
if (line.match(/^(Feature|Scenario|Background|Activity):/i)) {
|
|
79
|
+
this.inActivityBlock = false;
|
|
80
|
+
return false; // Process this line normally
|
|
68
81
|
}
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
if (line.match(/^waypoint\s+/i)) {
|
|
83
|
+
this.resolveWaypointCommon(line, path, allLines, lineIndex, line.includes(' with '));
|
|
84
|
+
// Check if this is the last waypoint in the block
|
|
85
|
+
let hasMoreWaypoints = false;
|
|
86
|
+
if (allLines && lineIndex !== undefined) {
|
|
87
|
+
for (let i = lineIndex + 1; i < allLines.length; i++) {
|
|
88
|
+
const nextLine = allLines[i].trim();
|
|
89
|
+
if (nextLine.match(/^(Feature|Scenario|Background|Activity):/i)) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (nextLine.match(/^waypoint\s+/i)) {
|
|
93
|
+
hasMoreWaypoints = true;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
76
96
|
}
|
|
77
97
|
}
|
|
78
|
-
|
|
98
|
+
if (!hasMoreWaypoints) {
|
|
99
|
+
this.inActivityBlock = false;
|
|
100
|
+
}
|
|
101
|
+
return true; // Skip the waypoint definition line
|
|
79
102
|
}
|
|
103
|
+
// Inside block
|
|
104
|
+
return true; // Skip lines inside the block
|
|
80
105
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
waypointWithProof: {
|
|
110
|
+
gwta: `waypoint {outcome} with {proof:${DOMAIN_STATEMENT}}`,
|
|
111
|
+
precludes: ['ActivitiesStepper.waypointLabel'],
|
|
85
112
|
action: async ({ proof }, featureStep) => {
|
|
86
|
-
// Execute the proof statements to satisfy this outcome
|
|
87
113
|
this.getWorld().logger.debug(`waypoint action: executing ${proof?.length || 0} proof steps`);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
114
|
+
try {
|
|
115
|
+
const result = await this.runner.runSteps(proof, { intent: { mode: 'authoritative' }, parentStep: featureStep });
|
|
116
|
+
if (result.kind !== 'ok') {
|
|
117
|
+
return actionNotOK(`waypoint: failed to execute proof steps: ${result.message}`);
|
|
118
|
+
}
|
|
119
|
+
return actionOK();
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
123
|
+
this.getWorld().logger.debug(`waypoint action: exception executing proof steps: ${msg}`);
|
|
124
|
+
const messageContext = {
|
|
125
|
+
incident: EExecutionMessageType.ACTION,
|
|
126
|
+
incidentDetails: { error: msg }
|
|
127
|
+
};
|
|
128
|
+
return actionNotOK(`waypoint: failed to execute proof steps: ${msg}`, { messageContext });
|
|
91
129
|
}
|
|
92
|
-
return actionOK();
|
|
93
130
|
},
|
|
94
131
|
},
|
|
132
|
+
waypointLabel: {
|
|
133
|
+
gwta: `waypoint {outcome}`,
|
|
134
|
+
action: async () => actionOK(),
|
|
135
|
+
},
|
|
95
136
|
ensure: {
|
|
96
|
-
description: 'Ensure a waypoint condition by always running the proof. If proof passes, waypoint is already satisfied. If proof fails, run the full activity
|
|
137
|
+
description: 'Ensure a waypoint condition by always running the proof. If proof passes, waypoint is already satisfied. If proof fails, run the full activity, then try the proof again',
|
|
97
138
|
gwta: `ensure {outcome:${DOMAIN_STATEMENT}}`,
|
|
139
|
+
unique: true, // Ensure takes priority over dynamically registered outcomes
|
|
98
140
|
action: async ({ outcome }, featureStep) => {
|
|
99
|
-
// Build outcome key from the resolved outcome steps
|
|
100
141
|
const outcomeKey = outcome.map(step => step.in).join(' ');
|
|
142
|
+
// Guard: prevent infinite ensure retry loops by counting attempts per outcome+seq
|
|
143
|
+
const attemptKey = outcomeKey; // key by outcome only to avoid differing seqPaths
|
|
144
|
+
const prevAttempts = this.ensureAttempts.get(attemptKey) ?? 0;
|
|
145
|
+
const MAX_ENSURE_ATTEMPTS = 10;
|
|
146
|
+
this.ensureAttempts.set(attemptKey, prevAttempts + 1);
|
|
147
|
+
if (prevAttempts + 1 > MAX_ENSURE_ATTEMPTS) {
|
|
148
|
+
this.getWorld().logger.warn(`ensure: exceeded max attempts (${MAX_ENSURE_ATTEMPTS}) for ${outcomeKey}`);
|
|
149
|
+
if (this.getWorld().runtime) {
|
|
150
|
+
this.getWorld().runtime.exhaustionError = 'ensure max attempts exceeded';
|
|
151
|
+
}
|
|
152
|
+
const messageContext = {
|
|
153
|
+
incident: EExecutionMessageType.ACTION,
|
|
154
|
+
incidentDetails: { waypoint: outcomeKey, satisfied: false, error: 'max ensure attempts exceeded', terminal: true }
|
|
155
|
+
};
|
|
156
|
+
return actionNotOK(`ensure: max attempts exceeded for waypoint "${outcomeKey}"`, { messageContext });
|
|
157
|
+
}
|
|
158
|
+
// Log ENSURE_START
|
|
159
|
+
const startMessageContext = {
|
|
160
|
+
incident: EExecutionMessageType.ENSURE_START,
|
|
161
|
+
incidentDetails: { waypoint: outcomeKey, step: featureStep }
|
|
162
|
+
};
|
|
163
|
+
this.getWorld().logger.log(`⏳ Ensuring ${outcomeKey}`, startMessageContext);
|
|
101
164
|
this.getWorld().logger.debug(`ensure: verifying waypoint "${outcomeKey}"`);
|
|
102
|
-
// Get the pattern from the first outcome step's action
|
|
103
165
|
const pattern = outcome[0]?.action?.actionName || outcomeKey;
|
|
104
|
-
// Get the registered waypoint to access its proof
|
|
105
166
|
const registeredWaypoint = this.steps[pattern];
|
|
106
167
|
if (!registeredWaypoint) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return actionNotOK(`ensure: waypoint "${outcomeKey}"
|
|
168
|
+
return actionNotOK(`ensure: "${outcomeKey}" is not a registered waypoint. ensure can only be used with waypoints.`);
|
|
169
|
+
}
|
|
170
|
+
const metadata = this.registeredOutcomeMetadata.get(pattern);
|
|
171
|
+
if (!metadata || metadata.proofStatements.length === 0) {
|
|
172
|
+
return actionNotOK(`ensure: waypoint "${outcomeKey}" has no proof. ensure can only be used with waypoints that have a proof.`);
|
|
173
|
+
}
|
|
174
|
+
// Extract args from the outcome step(s) to pass to the activity
|
|
175
|
+
// This ensures that variables (e.g. {name}) defined in the waypoint are available in the activity
|
|
176
|
+
const activityArgs = {};
|
|
177
|
+
for (const step of outcome) {
|
|
178
|
+
if (step.action.stepValuesMap) {
|
|
179
|
+
for (const [key, val] of Object.entries(step.action.stepValuesMap)) {
|
|
180
|
+
// Use the value if available (it should be resolved), otherwise term
|
|
181
|
+
const value = val.value !== undefined ? String(val.value) : val.term;
|
|
182
|
+
if (value !== undefined) {
|
|
183
|
+
activityArgs[key] = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
112
187
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
188
|
+
let proofStatements;
|
|
189
|
+
try {
|
|
190
|
+
// Use FlowRunner for the proof execution
|
|
191
|
+
// Pass activityArgs so they are available to the Outcome Action
|
|
192
|
+
const flowResult = await this.runner.runSteps(outcome, { intent: { mode: 'authoritative', usage: featureStep.intent?.usage, stepperOptions: { isEnsure: true } }, parentStep: featureStep });
|
|
193
|
+
if (flowResult.kind !== 'ok') {
|
|
194
|
+
// Log ENSURE_END for failure
|
|
195
|
+
const endMessageContext = {
|
|
196
|
+
incident: EExecutionMessageType.ENSURE_END,
|
|
197
|
+
incidentDetails: { waypoint: outcomeKey, satisfied: false, error: flowResult.message, actionResult: { ok: false } }
|
|
198
|
+
};
|
|
199
|
+
this.getWorld().logger.log(`❌ Failed ensuring ${outcomeKey}`, endMessageContext);
|
|
200
|
+
const messageContext = {
|
|
201
|
+
incident: EExecutionMessageType.ACTION,
|
|
202
|
+
incidentDetails: { waypoint: outcomeKey, satisfied: false, error: flowResult.message }
|
|
203
|
+
};
|
|
204
|
+
return actionNotOK(`ensure: waypoint "${outcomeKey}" proof failed: ${flowResult.message}`, { messageContext });
|
|
205
|
+
}
|
|
206
|
+
proofStatements = flowResult.payload?.messageContext?.incidentDetails?.proofStatements;
|
|
207
|
+
if (!proofStatements) {
|
|
208
|
+
return actionNotOK(`ensure: waypoint "${outcomeKey}" succeeded but returned no proofStatements`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
213
|
+
this.getWorld().logger.debug(`ensure: exception while executing proof for ${outcomeKey}: ${msg}`);
|
|
116
214
|
const messageContext = {
|
|
117
215
|
incident: EExecutionMessageType.ACTION,
|
|
118
|
-
incidentDetails: { waypoint: outcomeKey, satisfied: false, error:
|
|
216
|
+
incidentDetails: { waypoint: outcomeKey, satisfied: false, error: msg }
|
|
119
217
|
};
|
|
120
|
-
return actionNotOK(`ensure: waypoint "${outcomeKey}"
|
|
121
|
-
}
|
|
122
|
-
// Extract the interpolated proof statements from the result
|
|
123
|
-
const proofStatements = result.stepActionResult?.messageContext?.incidentDetails?.proofStatements;
|
|
124
|
-
// Track this ensured instance
|
|
125
|
-
if (proofStatements) {
|
|
126
|
-
this.ensuredInstances.set(outcomeKey, { proof: proofStatements, valid: true });
|
|
218
|
+
return actionNotOK(`ensure: waypoint "${outcomeKey}" proof execution error: ${msg}`, { messageContext });
|
|
127
219
|
}
|
|
220
|
+
// FIXME: We don't have easy access to proofStatements from FlowRunner result yet unless we pass them back
|
|
221
|
+
// For now, we assume if it passed, it passed.
|
|
222
|
+
this.ensuredInstances.set(outcomeKey, { proof: proofStatements, valid: true });
|
|
223
|
+
// On success or after one ensure action completes, reset attempt counter for this outcome
|
|
224
|
+
this.ensureAttempts.delete(attemptKey);
|
|
128
225
|
this.getWorld().logger.debug(`ensure: waypoint "${outcomeKey}" verified and satisfied`);
|
|
226
|
+
// Log ENSURE_END for success at trace level (just to hide the ENSURE_START)
|
|
227
|
+
const endMessageContext = {
|
|
228
|
+
incident: EExecutionMessageType.ENSURE_END,
|
|
229
|
+
incidentDetails: { waypoint: outcomeKey, satisfied: true, proofStatements, actionResult: { ok: true } }
|
|
230
|
+
};
|
|
231
|
+
this.getWorld().logger.trace(`✓ Ensured ${outcomeKey}`, endMessageContext);
|
|
129
232
|
const messageContext = {
|
|
130
233
|
incident: EExecutionMessageType.ACTION,
|
|
131
234
|
incidentDetails: {
|
|
@@ -137,52 +240,18 @@ export class ActivitiesStepper extends AStepper {
|
|
|
137
240
|
return actionOK({ messageContext });
|
|
138
241
|
},
|
|
139
242
|
},
|
|
140
|
-
forget: {
|
|
141
|
-
description: 'Deprecated: forget is no longer needed since ensure always re-verifies outcomes',
|
|
142
|
-
gwta: `forget {outcome:${DOMAIN_STATEMENT}}`,
|
|
143
|
-
action: ({ outcome }, featureStep) => {
|
|
144
|
-
const outcomeKey = outcome.map(step => step.in).join(' ');
|
|
145
|
-
this.getWorld().logger.debug(`forget: deprecated no-op for outcome "${outcomeKey}" (from ${featureStep.in}). Outcomes are no longer cached, so forget is unnecessary.`);
|
|
146
|
-
const messageContext = { incident: EExecutionMessageType.ACTION, incidentDetails: { outcome: outcomeKey, deprecated: true } };
|
|
147
|
-
return actionOK({ messageContext });
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
waypointed: {
|
|
151
|
-
description: 'Deprecated: waypointed is no longer meaningful since outcomes are not cached',
|
|
152
|
-
gwta: `waypointed {outcome:${DOMAIN_STATEMENT}}`,
|
|
153
|
-
action: ({ outcome }) => {
|
|
154
|
-
const outcomeKey = outcome.map(step => step.in).join(' ');
|
|
155
|
-
this.getWorld().logger.debug(`waypointed: deprecated for outcome "${outcomeKey}". Outcomes are verified on each ensure, not cached.`);
|
|
156
|
-
const messageContext = { incident: EExecutionMessageType.ACTION, incidentDetails: { outcome: outcomeKey, deprecated: true } };
|
|
157
|
-
return actionOK({ messageContext });
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
243
|
showWaypoints: {
|
|
161
244
|
exact: 'show waypoints',
|
|
162
245
|
action: async (_args, featureStep) => {
|
|
163
|
-
// Show ensured waypoint instances with their current validity
|
|
164
246
|
const waypointResults = {};
|
|
165
|
-
// Check validity of all ensured instances
|
|
166
247
|
for (const [instanceKey, instanceData] of this.ensuredInstances.entries()) {
|
|
167
248
|
this.getWorld().logger.debug(`show waypoints: verifying "${instanceKey}"`);
|
|
168
249
|
try {
|
|
169
|
-
//
|
|
170
|
-
const
|
|
171
|
-
for (let i = 0; i < instanceData.proof.length; i++) {
|
|
172
|
-
const statement = instanceData.proof[i];
|
|
173
|
-
const resolved = findFeatureStepsFromStatement(statement, this.steppers, this.getWorld(), featureStep.path, [...featureStep.seqPath, i], 1);
|
|
174
|
-
// Add waypoint context to each proof step
|
|
175
|
-
const contextualizedSteps = resolved.map(step => ({
|
|
176
|
-
...step,
|
|
177
|
-
in: `[${instanceKey} proof] ${step.in}`
|
|
178
|
-
}));
|
|
179
|
-
resolvedSteps.push(...contextualizedSteps);
|
|
180
|
-
}
|
|
181
|
-
// Execute the proof statements with NO_CYCLES to check current validity
|
|
182
|
-
const result = await executeSubFeatureSteps(featureStep, resolvedSteps, this.steppers, this.getWorld(), ExecMode.NO_CYCLES);
|
|
250
|
+
// Use FlowRunner to run the proof statements directly
|
|
251
|
+
const result = await this.runner.runStatements(instanceData.proof, { intent: { mode: 'speculative' }, parentStep: featureStep });
|
|
183
252
|
waypointResults[instanceKey] = {
|
|
184
253
|
proof: instanceData.proof.join('; '),
|
|
185
|
-
currentlyValid: result.ok
|
|
254
|
+
currentlyValid: result.kind === 'ok'
|
|
186
255
|
};
|
|
187
256
|
}
|
|
188
257
|
catch (error) {
|
|
@@ -200,38 +269,9 @@ export class ActivitiesStepper extends AStepper {
|
|
|
200
269
|
};
|
|
201
270
|
typedSteps = this.baseSteps;
|
|
202
271
|
steps = { ...this.baseSteps };
|
|
203
|
-
cycles = {
|
|
204
|
-
startFeature: (startFeature) => {
|
|
205
|
-
this.getWorld().logger.debug(`ActivitiesStepper.startFeature: starting feature at path "${startFeature.resolvedFeature.path}"`);
|
|
206
|
-
// If we were running a different feature before, clear its outcomes
|
|
207
|
-
if (this.lastFeaturePath && this.lastFeaturePath !== startFeature.resolvedFeature.path) {
|
|
208
|
-
this.getWorld().logger.debug(`ActivitiesStepper.startFeature: clearing outcomes from previous feature "${this.lastFeaturePath}"`);
|
|
209
|
-
const outcomesToClear = [];
|
|
210
|
-
for (const [outcome, featurePath] of this.outcomeToFeaturePath.entries()) {
|
|
211
|
-
if (featurePath === this.lastFeaturePath) {
|
|
212
|
-
outcomesToClear.push(outcome);
|
|
213
|
-
delete this.steps[outcome];
|
|
214
|
-
this.featureOutcomePatterns.delete(outcome);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
for (const outcome of outcomesToClear) {
|
|
218
|
-
this.outcomeToFeaturePath.delete(outcome);
|
|
219
|
-
}
|
|
220
|
-
this.getWorld().logger.debug(`ActivitiesStepper.startFeature: cleared ${outcomesToClear.length} outcomes from previous feature`);
|
|
221
|
-
}
|
|
222
|
-
this.currentFeaturePath = startFeature.resolvedFeature.path;
|
|
223
|
-
},
|
|
224
|
-
endFeature: async () => {
|
|
225
|
-
// Track this feature path so we can clean it up when the next feature starts
|
|
226
|
-
this.lastFeaturePath = this.currentFeaturePath;
|
|
227
|
-
// Clear ensured instances for next feature
|
|
228
|
-
this.ensuredInstances.clear();
|
|
229
|
-
return Promise.resolve();
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
272
|
async setWorld(world, steppers) {
|
|
233
273
|
await super.setWorld(world, steppers);
|
|
234
|
-
this.
|
|
274
|
+
this.runner = new FlowRunner(world, steppers);
|
|
235
275
|
}
|
|
236
276
|
/**
|
|
237
277
|
* Register a dynamic outcome step.
|
|
@@ -248,6 +288,15 @@ export class ActivitiesStepper extends AStepper {
|
|
|
248
288
|
if (this.steps[outcome]) {
|
|
249
289
|
throw new Error(`Outcome "${outcome}" is already registered. Each outcome can only be defined once.`);
|
|
250
290
|
}
|
|
291
|
+
// Normalize activity steps (split multiline strings)
|
|
292
|
+
const normalizedActivitySteps = activityBlockSteps?.flatMap(s => s.split('\n')).map(s => s.trim()).filter(s => s.length > 0) ?? [];
|
|
293
|
+
// Store metadata for runtime re-emission via TEST_LINKS messages
|
|
294
|
+
this.registeredOutcomeMetadata.set(outcome, {
|
|
295
|
+
proofStatements,
|
|
296
|
+
proofPath,
|
|
297
|
+
isBackground: isBackground ?? false,
|
|
298
|
+
activityBlockSteps: normalizedActivitySteps,
|
|
299
|
+
});
|
|
251
300
|
// Track whether this is a background or feature outcome
|
|
252
301
|
if (isBackground) {
|
|
253
302
|
this.backgroundOutcomePatterns.add(outcome);
|
|
@@ -259,84 +308,170 @@ export class ActivitiesStepper extends AStepper {
|
|
|
259
308
|
}
|
|
260
309
|
this.getWorld().logger.debug(`ActivitiesStepper: registerOutcome called with ${proofStatements.length} proof steps for "${outcome}"`);
|
|
261
310
|
this.getWorld().logger.debug(`ActivitiesStepper: outcome is background=${isBackground}, will be added to ${isBackground ? 'backgroundOutcomePatterns' : 'featureOutcomePatterns'}`);
|
|
262
|
-
//
|
|
263
|
-
const
|
|
264
|
-
this.steps[outcome] = {
|
|
311
|
+
// FIXME: maybe we don't care if normalizedActivitySteps is empty
|
|
312
|
+
const step = {
|
|
265
313
|
gwta: outcome,
|
|
266
314
|
virtual: true, // Dynamically registered outcomes are virtual
|
|
315
|
+
handlesUndefined: true, // FIXME they should not need to handle undefined at the virtual stepper level
|
|
267
316
|
description: `Outcome: ${outcome}. Proof: ${proofStatements.join('; ')}`,
|
|
268
317
|
action: async (args, featureStep) => {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
318
|
+
// Reconstruct args to include unresolved terms (skipped by strict populateActionArgs)
|
|
319
|
+
const robustArgs = { ...args };
|
|
320
|
+
if (featureStep.action.stepValuesMap) {
|
|
321
|
+
for (const [key, val] of Object.entries(featureStep.action.stepValuesMap)) {
|
|
322
|
+
if (robustArgs[key] === undefined && val.term !== undefined) {
|
|
323
|
+
robustArgs[key] = val.term;
|
|
324
|
+
}
|
|
275
325
|
}
|
|
276
|
-
|
|
277
|
-
});
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
326
|
+
}
|
|
327
|
+
this.getWorld().logger.debug(`ActivitiesStepper: executing recipe for outcome "${outcome}" with args ${JSON.stringify(robustArgs)}`);
|
|
328
|
+
// 1. Check Proof (Speculative)
|
|
329
|
+
if (proofStatements.length > 0) {
|
|
330
|
+
const proof = await this.runner.runStatements(proofStatements, { args: robustArgs, intent: { mode: 'speculative' }, parentStep: featureStep });
|
|
331
|
+
if (proof.kind === 'ok') {
|
|
332
|
+
this.getWorld().logger.debug(`ActivitiesStepper: proof passed for outcome "${outcome}", skipping activity body`);
|
|
333
|
+
return actionOK({
|
|
334
|
+
messageContext: {
|
|
335
|
+
incident: EExecutionMessageType.ACTION,
|
|
336
|
+
incidentDetails: { proofStatements, proofSatisfied: true }
|
|
337
|
+
}
|
|
338
|
+
});
|
|
287
339
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
this.getWorld().logger.debug(`ActivitiesStepper: proof passed for outcome "${outcome}", skipping activity body`);
|
|
298
|
-
const interpolatedProof = expandStatements(outcomeProofStatements);
|
|
299
|
-
return actionOK({
|
|
300
|
-
messageContext: {
|
|
301
|
-
incident: EExecutionMessageType.ACTION,
|
|
302
|
-
incidentDetails: { proofStatements: interpolatedProof, proofSatisfied: true }
|
|
340
|
+
}
|
|
341
|
+
// 2. Proof Failed or not present
|
|
342
|
+
if (!featureStep.intent?.stepperOptions?.isEnsure) {
|
|
343
|
+
if (normalizedActivitySteps && normalizedActivitySteps.length > 0) {
|
|
344
|
+
this.getWorld().logger.debug(`ActivitiesStepper: running activity body for outcome "${outcome}" (no ensure)`);
|
|
345
|
+
const mode = featureStep.intent?.mode === 'speculative' ? 'speculative' : 'authoritative';
|
|
346
|
+
const act = await this.runner.runStatements(normalizedActivitySteps, { args: robustArgs, intent: { mode, usage: featureStep.intent?.usage }, parentStep: featureStep });
|
|
347
|
+
if (act.kind !== 'ok') {
|
|
348
|
+
return actionNotOK(`ActivitiesStepper: activity body failed for outcome "${outcome}": ${act.message}`);
|
|
303
349
|
}
|
|
304
|
-
|
|
350
|
+
return actionOK();
|
|
351
|
+
}
|
|
352
|
+
if (proofStatements.length > 0) {
|
|
353
|
+
return actionNotOK(`ActivitiesStepper: proof failed for outcome "${outcome}"`);
|
|
354
|
+
}
|
|
355
|
+
// No proof (waypointLabel) and not ensure: do nothing.
|
|
356
|
+
return actionOK();
|
|
305
357
|
}
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
if (activityBlockSteps && activityBlockSteps.length > 0) {
|
|
358
|
+
// 3. Ensure Mode: Run Activity Body
|
|
359
|
+
if (normalizedActivitySteps && normalizedActivitySteps.length > 0) {
|
|
309
360
|
this.getWorld().logger.debug(`ActivitiesStepper: proof failed for outcome "${outcome}", running activity body`);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
return actionNotOK(`ActivitiesStepper: activity body failed for outcome "${outcome}"`);
|
|
361
|
+
const mode = featureStep.intent?.mode === 'speculative' ? 'speculative' : 'authoritative';
|
|
362
|
+
const act = await this.runner.runStatements(normalizedActivitySteps, { args: robustArgs, intent: { mode, usage: featureStep.intent?.usage }, parentStep: featureStep });
|
|
363
|
+
if (act.kind !== 'ok') {
|
|
364
|
+
return actionNotOK(`ActivitiesStepper: activity body failed for outcome "${outcome}": ${act.message}`);
|
|
315
365
|
}
|
|
316
|
-
//
|
|
317
|
-
// Use NO_CYCLES for proof verification to avoid triggering hooks
|
|
366
|
+
// 4. Verify Proof After Activity
|
|
318
367
|
this.getWorld().logger.debug(`ActivitiesStepper: verifying proof after activity body for outcome "${outcome}"`);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
368
|
+
if (proofStatements.length > 0) {
|
|
369
|
+
const verify = await this.runner.runStatements(proofStatements, { args: robustArgs, intent: { mode, usage: featureStep.intent?.usage }, parentStep: featureStep });
|
|
370
|
+
if (verify.kind !== 'ok') {
|
|
371
|
+
return actionNotOK(`ActivitiesStepper: proof verification failed after activity body for outcome "${outcome}": ${verify.message}`);
|
|
372
|
+
}
|
|
322
373
|
}
|
|
323
|
-
// Step 3: Success - proof now passes
|
|
324
|
-
const interpolatedProof = expandStatements(outcomeProofStatements);
|
|
325
374
|
return actionOK({
|
|
326
375
|
messageContext: {
|
|
327
376
|
incident: EExecutionMessageType.ACTION,
|
|
328
|
-
incidentDetails: { proofStatements:
|
|
377
|
+
incidentDetails: { proofStatements, proofSatisfied: true }
|
|
329
378
|
}
|
|
330
379
|
});
|
|
331
380
|
}
|
|
332
|
-
|
|
333
|
-
// No activity body, just proof - and it failed
|
|
334
|
-
return actionNotOK(`ActivitiesStepper: proof failed and no activity body available for outcome "${outcome}"`);
|
|
335
|
-
}
|
|
381
|
+
return actionNotOK(`ActivitiesStepper: no activity body for outcome "${outcome}"`);
|
|
336
382
|
}
|
|
337
383
|
};
|
|
384
|
+
this.steps[outcome] = step;
|
|
385
|
+
if (!isBackground) {
|
|
386
|
+
if (!this.featureSteps.has(proofPath)) {
|
|
387
|
+
this.featureSteps.set(proofPath, {});
|
|
388
|
+
}
|
|
389
|
+
this.featureSteps.get(proofPath)[outcome] = step;
|
|
390
|
+
}
|
|
338
391
|
this.getWorld().logger.debug(`ActivitiesStepper: registered outcome pattern "${outcome}" with ${proofStatements.length} proof steps`);
|
|
339
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Re-emit GRAPH_LINK messages for waypoint metadata.
|
|
395
|
+
* MonitorHandler subscribes after resolution, so we retransmit stored metadata.
|
|
396
|
+
*/
|
|
397
|
+
sendGraphLinkMessages() {
|
|
398
|
+
for (const [outcome, metadata] of this.registeredOutcomeMetadata.entries()) {
|
|
399
|
+
const messageContext = {
|
|
400
|
+
incident: EExecutionMessageType.GRAPH_LINK,
|
|
401
|
+
incidentDetails: {
|
|
402
|
+
outcome,
|
|
403
|
+
proofStatements: metadata.proofStatements,
|
|
404
|
+
proofPath: metadata.proofPath,
|
|
405
|
+
isBackground: metadata.isBackground,
|
|
406
|
+
activityBlockSteps: metadata.activityBlockSteps ?? null,
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
this.getWorld().logger.debug(`waypoint registered: "${outcome}"`, messageContext);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
resolveWaypointCommon(line, path, allLines, lineIndex, requireProof) {
|
|
413
|
+
if (!line.match(/^waypoint\s+/i)) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
let outcome;
|
|
417
|
+
let proofStatements = [];
|
|
418
|
+
if (requireProof) {
|
|
419
|
+
if (!line.match(/^waypoint\s+.+?\s+with\s+/i)) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
// Find the LAST occurrence of ' with ' to separate outcome from proof
|
|
423
|
+
// This allows 'with' to appear in outcome names (e.g., "{name} agreed with {concern}")
|
|
424
|
+
const withoutPrefix = line.replace(/^waypoint\s+/i, '');
|
|
425
|
+
const lastWithIndex = withoutPrefix.lastIndexOf(' with ');
|
|
426
|
+
if (lastWithIndex === -1)
|
|
427
|
+
return false;
|
|
428
|
+
outcome = withoutPrefix.substring(0, lastWithIndex).trim();
|
|
429
|
+
const proofRaw = withoutPrefix.substring(lastWithIndex + 6).trim(); // ' with ' is 6 chars
|
|
430
|
+
proofStatements = proofRaw.split('\n').map(s => s.trim()).filter(s => s.length > 0);
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
if (line.match(/^waypoint\s+.+?\s+with\s+/i)) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
const match = line.match(/^waypoint\s+(.+?)$/i);
|
|
437
|
+
if (!match)
|
|
438
|
+
return false;
|
|
439
|
+
outcome = match[1].trim();
|
|
440
|
+
}
|
|
441
|
+
// Skip if already registered (prevents infinite loops)
|
|
442
|
+
if (this.backgroundOutcomePatterns.has(outcome) || this.featureOutcomePatterns.has(outcome)) {
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
const isBackground = path.includes('backgrounds/');
|
|
446
|
+
// Scan backwards to find containing Activity block
|
|
447
|
+
let activityBlockSteps;
|
|
448
|
+
if (allLines && lineIndex !== undefined) {
|
|
449
|
+
let activityStartLine = -1;
|
|
450
|
+
for (let i = lineIndex - 1; i >= 0; i--) {
|
|
451
|
+
const prevLine = getActionable(allLines[i]);
|
|
452
|
+
if (prevLine.match(/^Activity:/i)) {
|
|
453
|
+
activityStartLine = i;
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
if (prevLine.match(/^(Feature|Scenario|Background):/i)) {
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (activityStartLine !== -1) {
|
|
461
|
+
// Collect steps between Activity: and waypoint (excluding waypoint itself)
|
|
462
|
+
const blockLines = [];
|
|
463
|
+
for (let i = activityStartLine + 1; i < lineIndex; i++) {
|
|
464
|
+
const stepLine = getActionable(allLines[i]);
|
|
465
|
+
if (stepLine && !stepLine.match(/^waypoint\s+/i)) {
|
|
466
|
+
blockLines.push(stepLine);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
activityBlockSteps = blockLines;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
this.registerOutcome(outcome, proofStatements, path, isBackground, activityBlockSteps);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
340
475
|
}
|
|
341
476
|
export default ActivitiesStepper;
|
|
342
477
|
//# sourceMappingURL=activities-stepper.js.map
|