@event4u/agent-config 8.4.0 → 8.4.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.
Files changed (126) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +6 -0
  3. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  4. package/dist/discovery/deprecation-report.md +1 -1
  5. package/dist/discovery/discovery-manifest.json +1 -1
  6. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  7. package/dist/discovery/discovery-manifest.summary.md +1 -1
  8. package/dist/discovery/orphan-report.md +1 -1
  9. package/dist/discovery/packs.json +1 -1
  10. package/dist/discovery/trust-report.md +1 -1
  11. package/dist/discovery/workspaces.json +1 -1
  12. package/dist/mcp/registry-manifest.json +1 -1
  13. package/package.json +4 -1
  14. package/src/agent-src/README.md +64 -0
  15. package/src/agent-src/scripts/archive_completed_roadmaps.ts +497 -0
  16. package/src/agent-src/scripts/scan-seeder-violations.php +145 -0
  17. package/src/agent-src/scripts/update_roadmap_progress.ts +1087 -0
  18. package/src/agent-src/templates/AGENTS.md +34 -0
  19. package/src/agent-src/templates/scripts/README.md +195 -0
  20. package/src/agent-src/templates/scripts/check_memory.ts +662 -0
  21. package/src/agent-src/templates/scripts/check_memory_proposal.ts +370 -0
  22. package/src/agent-src/templates/scripts/historical-bug-patterns.example.yml +84 -0
  23. package/src/agent-src/templates/scripts/implement_ticket/__main__.ts +27 -0
  24. package/src/agent-src/templates/scripts/memory_hash.ts +355 -0
  25. package/src/agent-src/templates/scripts/memory_lookup.ts +1089 -0
  26. package/src/agent-src/templates/scripts/memory_report.ts +868 -0
  27. package/src/agent-src/templates/scripts/memory_signal.ts +444 -0
  28. package/src/agent-src/templates/scripts/memory_status.ts +215 -0
  29. package/src/agent-src/templates/scripts/ownership-map.example.yml +87 -0
  30. package/src/agent-src/templates/scripts/pr-risk-config.example.yml +76 -0
  31. package/src/agent-src/templates/scripts/pr_review_routing.ts +544 -0
  32. package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
  33. package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  34. package/src/agent-src/templates/scripts/telemetry/boundary.ts +164 -0
  35. package/src/agent-src/templates/scripts/telemetry/engagement.ts +479 -0
  36. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +394 -0
  37. package/src/agent-src/templates/scripts/telemetry/settings.ts +210 -0
  38. package/src/agent-src/templates/scripts/telemetry_record.ts +276 -0
  39. package/src/agent-src/templates/scripts/telemetry_report.ts +211 -0
  40. package/src/agent-src/templates/scripts/telemetry_status.ts +333 -0
  41. package/src/agent-src/templates/scripts/tier_usage_report.ts +619 -0
  42. package/src/agent-src/templates/scripts/work_engine/__main__.ts +14 -0
  43. package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +1261 -0
  44. package/src/agent-src/templates/scripts/work_engine/_lib/user_global_paths.ts +329 -0
  45. package/src/agent-src/templates/scripts/work_engine/cli.ts +206 -0
  46. package/src/agent-src/templates/scripts/work_engine/cli_args.ts +249 -0
  47. package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +225 -0
  48. package/src/agent-src/templates/scripts/work_engine/directives/backend/analyze.ts +125 -0
  49. package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +189 -0
  50. package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +94 -0
  51. package/src/agent-src/templates/scripts/work_engine/directives/backend/memory.ts +193 -0
  52. package/src/agent-src/templates/scripts/work_engine/directives/backend/plan.ts +267 -0
  53. package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +518 -0
  54. package/src/agent-src/templates/scripts/work_engine/directives/backend/report.ts +379 -0
  55. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +268 -0
  56. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +258 -0
  57. package/src/agent-src/templates/scripts/work_engine/directives/index.ts +32 -0
  58. package/src/agent-src/templates/scripts/work_engine/directives/mixed/contract.ts +243 -0
  59. package/src/agent-src/templates/scripts/work_engine/directives/mixed/index.ts +108 -0
  60. package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +259 -0
  61. package/src/agent-src/templates/scripts/work_engine/directives/mixed/ui.ts +216 -0
  62. package/src/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.ts +40 -0
  63. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +241 -0
  64. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +216 -0
  65. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +506 -0
  66. package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +325 -0
  67. package/src/agent-src/templates/scripts/work_engine/directives/ui/index.ts +102 -0
  68. package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +462 -0
  69. package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +474 -0
  70. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +352 -0
  71. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.ts +33 -0
  72. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.ts +213 -0
  73. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/index.ts +111 -0
  74. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.ts +126 -0
  75. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/report.ts +112 -0
  76. package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/test.ts +164 -0
  77. package/src/agent-src/templates/scripts/work_engine/dispatcher.ts +515 -0
  78. package/src/agent-src/templates/scripts/work_engine/emitters.ts +119 -0
  79. package/src/agent-src/templates/scripts/work_engine/errors.ts +24 -0
  80. package/src/agent-src/templates/scripts/work_engine/hook_bootstrap.ts +104 -0
  81. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.ts +189 -0
  82. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.ts +41 -0
  83. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.ts +89 -0
  84. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +193 -0
  85. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +304 -0
  86. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.ts +110 -0
  87. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.ts +118 -0
  88. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/index.ts +17 -0
  89. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +161 -0
  90. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.ts +45 -0
  91. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/trace.ts +134 -0
  92. package/src/agent-src/templates/scripts/work_engine/hooks/context.ts +94 -0
  93. package/src/agent-src/templates/scripts/work_engine/hooks/events.ts +58 -0
  94. package/src/agent-src/templates/scripts/work_engine/hooks/exceptions.ts +85 -0
  95. package/src/agent-src/templates/scripts/work_engine/hooks/index.ts +27 -0
  96. package/src/agent-src/templates/scripts/work_engine/hooks/registry.ts +66 -0
  97. package/src/agent-src/templates/scripts/work_engine/hooks/runner.ts +90 -0
  98. package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +260 -0
  99. package/src/agent-src/templates/scripts/work_engine/input_builders.ts +260 -0
  100. package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +466 -0
  101. package/src/agent-src/templates/scripts/work_engine/migration/v0_to_v1.ts +531 -0
  102. package/src/agent-src/templates/scripts/work_engine/orchestration.ts +366 -0
  103. package/src/agent-src/templates/scripts/work_engine/persona_policy.ts +97 -0
  104. package/src/agent-src/templates/scripts/work_engine/resolvers/diff.ts +135 -0
  105. package/src/agent-src/templates/scripts/work_engine/resolvers/file.ts +175 -0
  106. package/src/agent-src/templates/scripts/work_engine/resolvers/prompt.ts +115 -0
  107. package/src/agent-src/templates/scripts/work_engine/scoring/confidence.ts +415 -0
  108. package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +466 -0
  109. package/src/agent-src/templates/scripts/work_engine/scoring/decision_trace.ts +298 -0
  110. package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +444 -0
  111. package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +252 -0
  112. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +745 -0
  113. package/src/agent-src/templates/scripts/work_engine/state.ts +1151 -0
  114. package/src/agent-src/templates/scripts/work_engine/state_io.ts +413 -0
  115. package/src/install/atomic.ts +111 -0
  116. package/src/install/conflict.ts +226 -0
  117. package/src/install/detect.ts +280 -0
  118. package/src/install/paths.ts +90 -0
  119. package/src/install/plan.ts +205 -0
  120. package/src/install/preflight.ts +210 -0
  121. package/src/install/selectedTools.ts +52 -0
  122. package/src/install/toolDetection.ts +120 -0
  123. package/src/install/txlog.ts +161 -0
  124. package/src/install/types.ts +175 -0
  125. package/src/install/wizard-plan.ts +216 -0
  126. package/src/scripts/prepack-check.mjs +108 -5
@@ -0,0 +1,259 @@
1
+ /**
2
+ * `stitch` step — integration verification across the contract / UI seam.
3
+ *
4
+ * TypeScript twin of `directives/mixed/stitch.py` (ADR-200 py2ts). Public API
5
+ * names stay snake_case to mirror the Python module 1:1.
6
+ *
7
+ * In the `mixed` directive set the `test` slot is the integration boundary.
8
+ * The dispatcher does not run integration scenarios itself — the agent invokes
9
+ * the `integration-test` skill which drives end-to-end smokes and writes the
10
+ * verdict back to `state.stitch`.
11
+ */
12
+ import {
13
+ type Any,
14
+ type DeliveryState,
15
+ Outcome,
16
+ StepResult,
17
+ agent_directive,
18
+ } from '../../delivery_state.js';
19
+
20
+ /** Agent directive that drives end-to-end smoke scenarios. */
21
+ export const INTEGRATION_TEST_DIRECTIVE = 'integration-test';
22
+
23
+ const _ALLOWED_VERDICTS: ReadonlyArray<string> = ['success', 'blocked', 'partial'];
24
+
25
+ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [
26
+ {
27
+ code: 'upstream_ui_failed',
28
+ trigger: '`implement` (mixed.ui) outcome is not `success`',
29
+ resolution: 're-run the mixed flow; UI track must finish before stitch',
30
+ },
31
+ {
32
+ code: 'empty_stitch_delegate',
33
+ trigger: '`state.stitch` empty — integration-test skill has not run yet',
34
+ resolution: 'agent directive `integration-test` → end-to-end smokes',
35
+ },
36
+ {
37
+ code: 'malformed_stitch',
38
+ trigger:
39
+ '`state.stitch` is not a dict or `verdict` is not one of ' +
40
+ 'success / blocked / partial',
41
+ resolution: 're-run `integration-test` and record a clean verdict',
42
+ },
43
+ {
44
+ code: 'bad_stitch_verdict',
45
+ trigger:
46
+ "`state.stitch['verdict']` is `blocked` or `partial` " +
47
+ 'and `integration_confirmed` is not True',
48
+ resolution:
49
+ 'address findings and re-run `integration-test`, or set ' +
50
+ '`integration_confirmed=True` to override (rare)',
51
+ },
52
+ ];
53
+
54
+ function _isDict(value: Any): value is Record<string, Any> {
55
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
56
+ }
57
+
58
+ function _pyTruthy(value: Any): boolean {
59
+ if (value === null || value === undefined) return false;
60
+ if (typeof value === 'string') return value.length > 0;
61
+ if (typeof value === 'number') return value !== 0;
62
+ if (typeof value === 'boolean') return value;
63
+ if (Array.isArray(value)) return value.length > 0;
64
+ if (typeof value === 'object') return Object.keys(value).length > 0;
65
+ return true;
66
+ }
67
+
68
+ function _pyTypeName(value: Any): string {
69
+ if (value === null || value === undefined) return 'NoneType';
70
+ if (typeof value === 'boolean') return 'bool';
71
+ if (typeof value === 'number') return Number.isInteger(value) ? 'int' : 'float';
72
+ if (typeof value === 'string') return 'str';
73
+ if (Array.isArray(value)) return 'list';
74
+ return 'dict';
75
+ }
76
+
77
+ function pyRepr(value: Any): string {
78
+ if (value === null || value === undefined) return 'None';
79
+ if (value === true) return 'True';
80
+ if (value === false) return 'False';
81
+ if (typeof value === 'string') return pyStrRepr(value);
82
+ return String(value);
83
+ }
84
+
85
+ function pyStrRepr(s: string): string {
86
+ const hasSingle = s.includes("'");
87
+ const hasDouble = s.includes('"');
88
+ const quote = hasSingle && !hasDouble ? '"' : "'";
89
+ let body = '';
90
+ for (const ch of s) {
91
+ if (ch === '\\') body += '\\\\';
92
+ else if (ch === quote) body += `\\${ch}`;
93
+ else if (ch === '\n') body += '\\n';
94
+ else if (ch === '\r') body += '\\r';
95
+ else if (ch === '\t') body += '\\t';
96
+ else body += ch;
97
+ }
98
+ return `${quote}${body}${quote}`;
99
+ }
100
+
101
+ function pyStr(value: Any): string {
102
+ if (value === null || value === undefined) return 'None';
103
+ if (value === true) return 'True';
104
+ if (value === false) return 'False';
105
+ return String(value);
106
+ }
107
+
108
+ function _pyRStrip(s: string): string {
109
+ return s.replace(/\s+$/u, '');
110
+ }
111
+
112
+ /** Gate on `implement`, then validate `state.stitch`. */
113
+ export function run(state: DeliveryState): StepResult {
114
+ if (state.outcomes['implement'] !== Outcome.SUCCESS) {
115
+ return _blocked_on_precondition(state);
116
+ }
117
+
118
+ const stitch = state.stitch;
119
+ if (!_pyTruthy(stitch)) {
120
+ return _delegate_to_integration_test(state);
121
+ }
122
+
123
+ const shape_issues = _diagnose_stitch(stitch);
124
+ if (shape_issues.length > 0) {
125
+ return _blocked_on_shape(state, shape_issues);
126
+ }
127
+
128
+ const s = stitch as Record<string, Any>;
129
+ const verdict = s['verdict'];
130
+ if (verdict === 'success') {
131
+ return new StepResult({ outcome: Outcome.SUCCESS });
132
+ }
133
+
134
+ if (s['integration_confirmed'] === true) {
135
+ return new StepResult({
136
+ outcome: Outcome.SUCCESS,
137
+ message: `stitch verdict \`${pyStr(verdict)}\` overridden by integration_confirmed=True.`,
138
+ });
139
+ }
140
+
141
+ return _blocked_on_bad_verdict(state, verdict, s);
142
+ }
143
+
144
+ /** Return shape errors; empty list when `stitch` is well-formed. */
145
+ function _diagnose_stitch(stitch: Any): string[] {
146
+ if (!_isDict(stitch)) {
147
+ return [`state.stitch must be a dict, got ${_pyTypeName(stitch)}`];
148
+ }
149
+ const verdict = stitch['verdict'];
150
+ if (typeof verdict !== 'string' || !_ALLOWED_VERDICTS.includes(verdict)) {
151
+ return [
152
+ `state.stitch['verdict'] must be one of ` +
153
+ `${_ALLOWED_VERDICTS.join(', ')}; got ${pyRepr(verdict)}`,
154
+ ];
155
+ }
156
+ return [];
157
+ }
158
+
159
+ /** One-line preview of the original input for halt bodies. */
160
+ function _preview_input(state: DeliveryState): string {
161
+ const data = (_isDict(state.ticket) ? state.ticket : {}) as Record<string, Any>;
162
+ const raw = data['raw'];
163
+ let text: string;
164
+ if (typeof raw === 'string' && raw.trim() !== '') {
165
+ text = raw.split(/\s+/u).filter((x) => x.length > 0).join(' ');
166
+ } else {
167
+ const title = data['title'];
168
+ if (typeof title === 'string') {
169
+ text = title;
170
+ } else {
171
+ const id = data['id'];
172
+ text = _pyTruthy(id) ? pyStr(id) : '(no title)';
173
+ }
174
+ }
175
+ if ([...text].length <= 80) {
176
+ return text;
177
+ }
178
+ return _pyRStrip([...text].slice(0, 79).join('')) + '…';
179
+ }
180
+
181
+ /** BLOCKED halt — upstream UI step did not succeed. */
182
+ function _blocked_on_precondition(state: DeliveryState): StepResult {
183
+ void state;
184
+ return new StepResult({
185
+ outcome: Outcome.BLOCKED,
186
+ questions: [
187
+ '> Mixed `stitch` step gated on the UI step; ' +
188
+ 'the implement outcome is not success.',
189
+ '> 1. Re-run — restart the mixed flow from the start',
190
+ '> 2. Abort — drop this request',
191
+ ],
192
+ message: 'stitch step gated on implement; upstream UI outcome is not success.',
193
+ });
194
+ }
195
+
196
+ /** Halt with an agent directive so the orchestrator runs smokes. */
197
+ function _delegate_to_integration_test(state: DeliveryState): StepResult {
198
+ const preview = _preview_input(state);
199
+ const contract = _isDict(state.contract) ? state.contract : {};
200
+ const api_surface = _pyTruthy(contract['api_surface']) ? contract['api_surface'] : [];
201
+ const endpoint_count = Array.isArray(api_surface) ? api_surface.length : 0;
202
+
203
+ return new StepResult({
204
+ outcome: Outcome.BLOCKED,
205
+ questions: [
206
+ agent_directive(INTEGRATION_TEST_DIRECTIVE),
207
+ `> Input: ${preview}`,
208
+ '> UI track finished; the contract / UI seam needs end-to-end ' +
209
+ 'verification before the delivery report:',
210
+ `> - Endpoints / actions to smoke: ${endpoint_count}`,
211
+ '> Scenarios cover the full round-trip — fill form → ' +
212
+ 'server validation → response → UI update. Unit-level ' +
213
+ 'passes from the UI review do **not** substitute for this gate.',
214
+ '> 1. Continue — run `integration-test` now',
215
+ '> 2. Abort — skip integration verification (NOT recommended)',
216
+ ],
217
+ message: 'Mixed UI complete; delegating to integration-test for stitch.',
218
+ });
219
+ }
220
+
221
+ /** BLOCKED halt — recorded stitch verdict is malformed. */
222
+ function _blocked_on_shape(state: DeliveryState, issues: string[]): StepResult {
223
+ void state;
224
+ return new StepResult({
225
+ outcome: Outcome.BLOCKED,
226
+ questions: [
227
+ '> Recorded stitch output is malformed: ' + issues.join('; ') + '.',
228
+ '> 1. Re-run `integration-test` and resume',
229
+ '> 2. Abort — stitch verdict cannot be trusted',
230
+ ],
231
+ message: `Mixed stitch shape invalid: ${issues.join('; ')}.`,
232
+ });
233
+ }
234
+
235
+ /** BLOCKED halt — verdict is blocked / partial and not user-confirmed. */
236
+ function _blocked_on_bad_verdict(
237
+ state: DeliveryState,
238
+ verdict: Any,
239
+ stitch: Record<string, Any>,
240
+ ): StepResult {
241
+ void state;
242
+ const scenarios = _pyTruthy(stitch['scenarios']) ? stitch['scenarios'] : [];
243
+ const scenario_count = Array.isArray(scenarios) ? scenarios.length : 0;
244
+ return new StepResult({
245
+ outcome: Outcome.BLOCKED,
246
+ questions: [
247
+ `> \`integration-test\` reported \`${pyStr(verdict)}\` after running ` +
248
+ `${scenario_count} scenario(s). The delivery report cannot ` +
249
+ 'claim completion on a non-success integration verdict.',
250
+ '> 1. Address the findings and re-run `integration-test`',
251
+ '> 2. Override — set `state.stitch.integration_confirmed=true` ' +
252
+ 'and resume (rare; document why)',
253
+ '> 3. Abort',
254
+ ],
255
+ message:
256
+ `Mixed stitch verdict was \`${pyStr(verdict)}\`, not success; ` +
257
+ 'user override required to continue.',
258
+ });
259
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * `ui` step — delegates to the UI track once the contract is locked.
3
+ *
4
+ * TypeScript twin of `directives/mixed/ui.py` (ADR-200 py2ts). Public API
5
+ * names stay snake_case to mirror the Python module 1:1.
6
+ *
7
+ * In the `mixed` directive set the `implement` slot is the UI handoff. It
8
+ * gates on the upstream contract sentinel
9
+ * (`state.contract.contract_confirmed is True`) and then delegates the full
10
+ * audit → design → apply → review → polish sub-flow to the UI track via an
11
+ * `@agent-directive: ui-track` halt.
12
+ */
13
+ import {
14
+ type Any,
15
+ type DeliveryState,
16
+ Outcome,
17
+ StepResult,
18
+ agent_directive,
19
+ } from '../../delivery_state.js';
20
+
21
+ /** Agent directive that triggers the full UI sub-flow. */
22
+ export const UI_TRACK_DIRECTIVE = 'ui-track';
23
+
24
+ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [
25
+ {
26
+ code: 'upstream_contract_failed',
27
+ trigger: '`plan` (contract) outcome is not `success`',
28
+ resolution: 're-run the mixed flow from the start',
29
+ },
30
+ {
31
+ code: 'contract_sentinel_missing',
32
+ trigger:
33
+ 'state.contract.contract_confirmed is missing or False — ' +
34
+ 'defense-in-depth check refuses to start the UI track',
35
+ resolution:
36
+ 'loop back to the contract step; user must confirm ' +
37
+ 'the data_model + api_surface lock before UI work begins',
38
+ },
39
+ {
40
+ code: 'ui_track_not_started',
41
+ trigger:
42
+ 'state.ui_review is empty or missing `review_clean` — ' +
43
+ 'the UI sub-flow has not run yet',
44
+ resolution:
45
+ 'agent directive `ui-track` → orchestrator runs ' +
46
+ 'audit → design → apply → review → polish with the contract as ' +
47
+ 'immutable input',
48
+ },
49
+ {
50
+ code: 'ui_track_review_unclean',
51
+ trigger:
52
+ 'UI sub-flow finished but `review_clean` is False — ' +
53
+ 'polish ceiling reached or findings remain',
54
+ resolution:
55
+ 'user picks re-run / hand off / abort; polish-ceiling ' +
56
+ 'semantics already fired inside the UI track',
57
+ },
58
+ ];
59
+
60
+ function _isDict(value: Any): value is Record<string, Any> {
61
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
62
+ }
63
+
64
+ function _pyTruthy(value: Any): boolean {
65
+ if (value === null || value === undefined) return false;
66
+ if (typeof value === 'string') return value.length > 0;
67
+ if (typeof value === 'number') return value !== 0;
68
+ if (typeof value === 'boolean') return value;
69
+ if (Array.isArray(value)) return value.length > 0;
70
+ if (typeof value === 'object') return Object.keys(value).length > 0;
71
+ return true;
72
+ }
73
+
74
+ function pyStr(value: Any): string {
75
+ if (value === null || value === undefined) return 'None';
76
+ if (value === true) return 'True';
77
+ if (value === false) return 'False';
78
+ return String(value);
79
+ }
80
+
81
+ function _pyRStrip(s: string): string {
82
+ return s.replace(/\s+$/u, '');
83
+ }
84
+
85
+ /** Delegate the UI sub-flow once the contract is locked. */
86
+ export function run(state: DeliveryState): StepResult {
87
+ if (state.outcomes['plan'] !== Outcome.SUCCESS) {
88
+ return _blocked_on_precondition(state);
89
+ }
90
+
91
+ if (!_contract_confirmed(state)) {
92
+ return _blocked_on_contract_sentinel(state);
93
+ }
94
+
95
+ const review = _isDict(state.ui_review) ? state.ui_review : null;
96
+ if (review === null || !('review_clean' in review)) {
97
+ return _delegate_to_ui_track(state);
98
+ }
99
+
100
+ if (review['review_clean'] === true) {
101
+ return new StepResult({ outcome: Outcome.SUCCESS });
102
+ }
103
+
104
+ return _halt_review_unclean(state, review);
105
+ }
106
+
107
+ /** True when the contract sentinel is explicitly `True`. */
108
+ function _contract_confirmed(state: DeliveryState): boolean {
109
+ const contract = state.contract;
110
+ if (!_isDict(contract)) return false;
111
+ return contract['contract_confirmed'] === true;
112
+ }
113
+
114
+ /** One-line preview of the original input for halt bodies. */
115
+ function _preview_input(state: DeliveryState): string {
116
+ const data = (_isDict(state.ticket) ? state.ticket : {}) as Record<string, Any>;
117
+ const raw = data['raw'];
118
+ let text: string;
119
+ if (typeof raw === 'string' && raw.trim() !== '') {
120
+ text = raw.split(/\s+/u).filter((x) => x.length > 0).join(' ');
121
+ } else {
122
+ const title = data['title'];
123
+ if (typeof title === 'string') {
124
+ text = title;
125
+ } else {
126
+ const id = data['id'];
127
+ text = _pyTruthy(id) ? pyStr(id) : '(no title)';
128
+ }
129
+ }
130
+ if ([...text].length <= 80) {
131
+ return text;
132
+ }
133
+ return _pyRStrip([...text].slice(0, 79).join('')) + '…';
134
+ }
135
+
136
+ /** BLOCKED halt — upstream contract step did not succeed. */
137
+ function _blocked_on_precondition(state: DeliveryState): StepResult {
138
+ void state;
139
+ return new StepResult({
140
+ outcome: Outcome.BLOCKED,
141
+ questions: [
142
+ '> Mixed `ui` step gated on the contract step; the contract ' +
143
+ 'outcome is not success.',
144
+ '> 1. Re-run — restart the mixed flow from the start',
145
+ '> 2. Abort — drop this request',
146
+ ],
147
+ message: 'ui step gated on plan; upstream contract outcome is not success.',
148
+ });
149
+ }
150
+
151
+ /** BLOCKED halt — contract sentinel missing despite plan success. */
152
+ function _blocked_on_contract_sentinel(state: DeliveryState): StepResult {
153
+ void state;
154
+ return new StepResult({
155
+ outcome: Outcome.BLOCKED,
156
+ questions: [
157
+ '> Contract sentinel missing — `state.contract.contract_confirmed` ' +
158
+ 'is not `True`. The UI track will not start until the data ' +
159
+ 'model and API surface are locked and confirmed.',
160
+ '> 1. Loop back — re-enter the contract step and confirm',
161
+ '> 2. Abort — drop this mixed request',
162
+ ],
163
+ message:
164
+ 'ui step refused; contract_confirmed sentinel missing despite plan success.',
165
+ });
166
+ }
167
+
168
+ /** Halt with an agent directive so the orchestrator runs the UI track. */
169
+ function _delegate_to_ui_track(state: DeliveryState): StepResult {
170
+ const preview = _preview_input(state);
171
+ const contract = _isDict(state.contract) ? state.contract : {};
172
+ const data_model = _pyTruthy(contract['data_model']) ? contract['data_model'] : [];
173
+ const api_surface = _pyTruthy(contract['api_surface']) ? contract['api_surface'] : [];
174
+ const entity_count = Array.isArray(data_model) ? data_model.length : 0;
175
+ const endpoint_count = Array.isArray(api_surface) ? api_surface.length : 0;
176
+
177
+ return new StepResult({
178
+ outcome: Outcome.BLOCKED,
179
+ questions: [
180
+ agent_directive(UI_TRACK_DIRECTIVE),
181
+ `> Input: ${preview}`,
182
+ '> Contract is locked. Handing off to the UI track:',
183
+ `> - Entities: ${entity_count}`,
184
+ `> - Endpoints / actions: ${endpoint_count}`,
185
+ '> The UI track runs audit → design → apply → ' +
186
+ 'review → polish with the contract as immutable input. ' +
187
+ 'No new entities or endpoints — the contract drives the UI shape.',
188
+ '> 1. Continue — run the UI track',
189
+ '> 2. Abort — drop this mixed request',
190
+ ],
191
+ message: 'Mixed contract locked; delegating UI sub-flow to ui-track.',
192
+ });
193
+ }
194
+
195
+ /** BLOCKED halt — UI sub-flow finished but review is not clean. */
196
+ function _halt_review_unclean(state: DeliveryState, review: Record<string, Any>): StepResult {
197
+ void state;
198
+ const findings = _pyTruthy(review['findings']) ? review['findings'] : [];
199
+ const finding_count = Array.isArray(findings) ? findings.length : 0;
200
+ const lines: string[] = [
201
+ `> UI track finished but review is not clean. Findings remaining: ${finding_count}.`,
202
+ '> Polish ceiling already fired inside the UI track — the ' +
203
+ 'engine cannot resolve the remaining findings without a user ' +
204
+ 'decision.',
205
+ '> 1. Re-run UI track — hand back to `ui-track` for another ' +
206
+ 'audit → design pass (rare; usually means the contract changed)',
207
+ '> 2. Hand off — ship as-is and let a human resolve the ' +
208
+ 'remaining findings outside the engine',
209
+ '> 3. Abort — drop this mixed request',
210
+ ];
211
+ return new StepResult({
212
+ outcome: Outcome.BLOCKED,
213
+ questions: lines,
214
+ message: `Mixed ui step halted; UI track review unclean (${finding_count} findings).`,
215
+ });
216
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Pass-through handler for UI directive slots that have no work.
3
+ *
4
+ * TypeScript twin of `directives/ui/_passthrough.py` (ADR-200 py2ts). Public
5
+ * API names stay snake_case to mirror the Python module 1:1 (Python style is
6
+ * part of the contract). The handler is intentionally pure and
7
+ * side-effect-free: it neither reads nor writes `state`.
8
+ *
9
+ * Phase 6 of the UI track retired the Phase 3 deferral stub once design /
10
+ * apply / review / polish landed. Two slots remain semantically empty for the
11
+ * UI track:
12
+ *
13
+ * - `memory` — the UI track does not consult the memory types the
14
+ * backend retrieves over. UI work pivots on the audit findings in
15
+ * `state.ui_audit` instead.
16
+ * - `plan` — `.design` produces the locked design brief that `.apply`
17
+ * follows verbatim. The brief IS the plan.
18
+ *
19
+ * Both slots return `Outcome.SUCCESS` without writing to state.
20
+ */
21
+ import {
22
+ type DeliveryState,
23
+ Outcome,
24
+ StepResult,
25
+ } from '../../delivery_state.js';
26
+
27
+ /** Pass-through never blocks — empty surface, declared intent. */
28
+ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [];
29
+
30
+ /**
31
+ * Return `SUCCESS` without mutating state.
32
+ *
33
+ * The handler neither reads nor writes `state`. The dispatcher records the
34
+ * step as successful in `state.outcomes` (its own bookkeeping) and advances to
35
+ * the next slot.
36
+ */
37
+ export function run(state: DeliveryState): StepResult {
38
+ void state; // explicitly unused — the slot is a no-op by design
39
+ return new StepResult({ outcome: Outcome.SUCCESS });
40
+ }