@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,366 @@
1
+ /**
2
+ * State machine stub for the `/orchestrate` command.
3
+ *
4
+ * TypeScript twin of `work_engine/orchestration.py` (ADR-200 py2ts Phase 1 —
5
+ * work_engine foundation). Public API names stay snake_case to mirror the
6
+ * Python module 1:1 (per ADR-200 — Python style is part of the contract).
7
+ *
8
+ * Reads a pipeline file conforming to
9
+ * `docs/contracts/orchestration-dsl-v1.md` and produces an ordered
10
+ * sequence of step descriptors the agent dispatches one at a time.
11
+ * The runtime itself is **not** in Python — each step is executed by the
12
+ * agent via skill / command / persona / subagent dispatch. This module
13
+ * holds the deterministic bookkeeping:
14
+ *
15
+ * - load + interpolate
16
+ * - step iteration with success / failure / when-guard tracking
17
+ * - output-map resolution at the end
18
+ *
19
+ * Design constraints (R1 carve-outs from
20
+ * `road-to-distribution-and-adoption.md`):
21
+ *
22
+ * - No external dependencies. YAML loading reuses the dispatcher's
23
+ * loader so the runtime sees what the linter sees.
24
+ * - No side effects. The state machine never edits files, runs commands,
25
+ * or emits hooks of its own. Audit emission is the caller's job.
26
+ * - Forward-ref free. `steps[].with` references can only reach
27
+ * earlier steps; this is enforced both by the linter and at runtime.
28
+ */
29
+ import * as fs from 'node:fs';
30
+ import * as path from 'node:path';
31
+ import { fileURLToPath, pathToFileURL } from 'node:url';
32
+
33
+ // Mirror of Python `re` group semantics: capture (inputs|steps), the ident,
34
+ // and an optional `.output` suffix; `g` flag so `.sub`-style replace covers
35
+ // every non-overlapping match like Python `re.sub`.
36
+ const _INTERP_RE = /\$\{\{\s*(inputs|steps)\.([a-z0-9_-]+)(?:\.output)?\s*\}\}/g;
37
+
38
+ /** Heterogeneous value, mirroring Python's `Any`. */
39
+ export type Any = unknown;
40
+
41
+ /** One step's record after dispatch. */
42
+ export class StepResult {
43
+ step_id: string;
44
+ kind: string;
45
+ ref: string;
46
+ success: boolean;
47
+ output: string;
48
+ error: string | null;
49
+
50
+ constructor(
51
+ step_id: string,
52
+ kind: string,
53
+ ref: string,
54
+ success = false,
55
+ output = '',
56
+ error: string | null = null,
57
+ ) {
58
+ this.step_id = step_id;
59
+ this.kind = kind;
60
+ this.ref = ref;
61
+ this.success = success;
62
+ this.output = output;
63
+ this.error = error;
64
+ }
65
+ }
66
+
67
+ /** Bookkeeping for a single `/orchestrate` run. */
68
+ export class PipelineState {
69
+ name: string;
70
+ inputs: Record<string, string>;
71
+ results: Record<string, StepResult>;
72
+ halted: boolean;
73
+ halt_reason: string | null;
74
+
75
+ constructor(args: {
76
+ name: string;
77
+ inputs: Record<string, string>;
78
+ results?: Record<string, StepResult>;
79
+ halted?: boolean;
80
+ halt_reason?: string | null;
81
+ }) {
82
+ this.name = args.name;
83
+ this.inputs = args.inputs;
84
+ this.results = args.results ?? {};
85
+ this.halted = args.halted ?? false;
86
+ this.halt_reason = args.halt_reason ?? null;
87
+ }
88
+ }
89
+
90
+ /** True when `value` is a plain object (Python `isinstance(x, dict)`). */
91
+ function _isDict(value: unknown): value is Record<string, Any> {
92
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
93
+ }
94
+
95
+ /** Python `re.Match`-style replacement function signature loader. */
96
+ interface DispatchHookModule {
97
+ _load_yaml(p: string): Record<string, Any>;
98
+ }
99
+
100
+ /**
101
+ * Reuse the linter's loader so the runtime accepts the same shape.
102
+ *
103
+ * Walks parents to find `scripts/hooks/dispatch_hook.ts` so the loader is
104
+ * reachable both when this module runs from the consumer projection
105
+ * (`dist/agent-src/templates/scripts/work_engine/`) and from the
106
+ * source-of-truth tree
107
+ * (`packages/<pack>/.agent-src.uncondensed/templates/scripts/work_engine/`).
108
+ * Loaded by dynamic `import()` to avoid namespace collisions with test
109
+ * modules named `hooks` (the TS analogue of the Python importlib-by-path
110
+ * approach).
111
+ */
112
+ async function _load_pipeline(p: string): Promise<Record<string, Any>> {
113
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
114
+ let candidate: string | null = null;
115
+ // Layout-agnostic: the dispatcher sits at `scripts/hooks/` in a consumer
116
+ // install + the `dist/agent-src/` projection, and at `src/scripts/hooks/`
117
+ // in the maintainer source tree. Probe both per parent so the loader
118
+ // resolves regardless of where this template runs.
119
+ const relCandidates = [
120
+ path.join('scripts', 'hooks', 'dispatch_hook.ts'),
121
+ path.join('src', 'scripts', 'hooks', 'dispatch_hook.ts'),
122
+ ];
123
+ let dir = path.dirname(here);
124
+ // Walk `here.parents` — every ancestor directory up to the filesystem root.
125
+ for (;;) {
126
+ for (const rel of relCandidates) {
127
+ const probe = path.join(dir, rel);
128
+ if (fs.existsSync(probe) && fs.statSync(probe).isFile()) {
129
+ candidate = probe;
130
+ break;
131
+ }
132
+ }
133
+ if (candidate !== null) {
134
+ break;
135
+ }
136
+ const parent = path.dirname(dir);
137
+ if (parent === dir) {
138
+ break;
139
+ }
140
+ dir = parent;
141
+ }
142
+ if (candidate === null) {
143
+ throw new Error(
144
+ 'could not locate scripts/hooks/dispatch_hook.ts (or ' +
145
+ `src/scripts/hooks/dispatch_hook.ts) from ${here}`,
146
+ );
147
+ }
148
+ const module = (await import(pathToFileURL(candidate).href)) as DispatchHookModule;
149
+ const doc = module._load_yaml(p);
150
+ if (!_isDict(doc)) {
151
+ throw new Error(`${p}: top-level must be a mapping`);
152
+ }
153
+ return doc;
154
+ }
155
+
156
+ /**
157
+ * Substitute `${{ inputs.X }}` / `${{ steps.Y.output }}` in a nested value.
158
+ * Unknown references throw — the linter should have caught them, but the
159
+ * runtime double-checks.
160
+ */
161
+ export function _interpolate(value: Any, state: PipelineState): Any {
162
+ if (typeof value === 'string') {
163
+ return value.replace(_INTERP_RE, (_match, ns: string, ident: string) => {
164
+ if (ns === 'inputs') {
165
+ if (!(ident in state.inputs)) {
166
+ // Mirror Python `KeyError(f"unknown input '{ident}'")`.
167
+ throw new KeyError(`unknown input '${ident}'`);
168
+ }
169
+ return state.inputs[ident] as string;
170
+ }
171
+ if (!(ident in state.results)) {
172
+ throw new KeyError(`unknown step '${ident}'`);
173
+ }
174
+ return (state.results[ident] as StepResult).output;
175
+ });
176
+ }
177
+ if (_isDict(value)) {
178
+ const out: Record<string, Any> = {};
179
+ for (const [k, v] of Object.entries(value)) {
180
+ out[k] = _interpolate(v, state);
181
+ }
182
+ return out;
183
+ }
184
+ if (Array.isArray(value)) {
185
+ return value.map((v) => _interpolate(v, state));
186
+ }
187
+ return value;
188
+ }
189
+
190
+ /**
191
+ * Evaluate the limited `when` mini-language. Supports
192
+ * `steps.X.success` / `steps.X.failure` and equality on a single
193
+ * `${{ steps.X.output }}` template against a literal.
194
+ */
195
+ export function _when_passes(when: string | null | undefined, state: PipelineState): boolean {
196
+ if (!when) {
197
+ return true;
198
+ }
199
+ when = when.trim();
200
+ const m1 = fullmatch(/steps\.([a-z0-9_-]+)\.(success|failure)/, when);
201
+ if (m1) {
202
+ const sid = m1[1] as string;
203
+ const kind = m1[2] as string;
204
+ if (!(sid in state.results)) {
205
+ return false;
206
+ }
207
+ const res = state.results[sid] as StepResult;
208
+ return kind === 'success' ? res.success : !res.success;
209
+ }
210
+ const m2 = fullmatch(/\$\{\{\s*steps\.([a-z0-9_-]+)\.output\s*\}\}\s*==\s*"([^"]*)"/, when);
211
+ if (m2) {
212
+ const sid = m2[1] as string;
213
+ const literal = m2[2] as string;
214
+ const res = state.results[sid] ?? new StepResult(sid, '', '');
215
+ return res.output === literal;
216
+ }
217
+ // Mirror Python `ValueError(f"unsupported when expression: {when!r}")`.
218
+ throw new ValueError(`unsupported when expression: ${pyRepr(when)}`);
219
+ }
220
+
221
+ /** A descriptor yielded by `iter_steps`, carrying the live state for callbacks. */
222
+ export interface StepDescriptor {
223
+ id: string;
224
+ kind: string;
225
+ ref: string;
226
+ with: Any;
227
+ _state: PipelineState;
228
+ }
229
+
230
+ /**
231
+ * Yield interpolated step descriptors in order.
232
+ *
233
+ * Caller dispatches each descriptor via skill / command / persona /
234
+ * subagent and feeds the result back via {@link record_result}.
235
+ */
236
+ export async function* iter_steps(
237
+ p: string,
238
+ inputs: Record<string, string>,
239
+ ): AsyncGenerator<StepDescriptor> {
240
+ const doc = await _load_pipeline(p);
241
+ const merged_inputs: Record<string, string> = {};
242
+ const docInputs = (doc['inputs'] as Any[] | null | undefined) || [];
243
+ for (const inp of docInputs) {
244
+ if (_isDict(inp) && typeof inp['id'] === 'string') {
245
+ const id = inp['id'] as string;
246
+ merged_inputs[id] =
247
+ id in inputs
248
+ ? (inputs[id] as string)
249
+ : (((inp['default'] as string | undefined) ?? '') as string);
250
+ }
251
+ }
252
+ const state = new PipelineState({
253
+ name: (doc['name'] as string | undefined) ?? '',
254
+ inputs: merged_inputs,
255
+ });
256
+ const steps = (doc['steps'] as Array<Record<string, Any>> | null | undefined) || [];
257
+ for (const step of steps) {
258
+ if (state.halted) {
259
+ break;
260
+ }
261
+ if (!_when_passes(step['when'] as string | null | undefined, state)) {
262
+ continue;
263
+ }
264
+ yield {
265
+ id: step['id'] as string,
266
+ kind: step['kind'] as string,
267
+ ref: step['ref'] as string,
268
+ with: _interpolate((step['with'] as Any) || {}, state),
269
+ _state: state,
270
+ };
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Caller hands the descriptor + outcome back so subsequent steps can see
276
+ * `${{ steps.<id>.output }}`.
277
+ */
278
+ export function record_result(
279
+ descriptor: StepDescriptor,
280
+ opts: { success: boolean; output?: string; error?: string | null },
281
+ ): void {
282
+ const state = descriptor._state;
283
+ state.results[descriptor.id] = new StepResult(
284
+ descriptor.id,
285
+ descriptor.kind,
286
+ descriptor.ref,
287
+ opts.success,
288
+ opts.output ?? '',
289
+ opts.error ?? null,
290
+ );
291
+ if (!opts.success) {
292
+ state.halted = true;
293
+ state.halt_reason = `step ${descriptor.id} failed`;
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Resolve the pipeline's `outputs:` map against the captured step outputs.
299
+ * Returns an empty map if the pipeline declares no outputs.
300
+ */
301
+ export async function resolve_outputs(
302
+ p: string,
303
+ state: PipelineState,
304
+ ): Promise<Record<string, Any>> {
305
+ const doc = await _load_pipeline(p);
306
+ const raw = (doc['outputs'] as Record<string, Any> | null | undefined) || {};
307
+ const out: Record<string, Any> = {};
308
+ for (const [k, v] of Object.entries(raw)) {
309
+ out[k] = _interpolate(v, state);
310
+ }
311
+ return out;
312
+ }
313
+
314
+ // ── Python-parity helpers ───────────────────────────────────────────────
315
+
316
+ /** Python `KeyError` — distinct class so callers can mirror the type. */
317
+ export class KeyError extends Error {
318
+ constructor(message?: string) {
319
+ super(message);
320
+ Object.setPrototypeOf(this, KeyError.prototype);
321
+ this.name = 'KeyError';
322
+ }
323
+ }
324
+
325
+ /** Python `ValueError`. */
326
+ export class ValueError extends Error {
327
+ constructor(message?: string) {
328
+ super(message);
329
+ Object.setPrototypeOf(this, ValueError.prototype);
330
+ this.name = 'ValueError';
331
+ }
332
+ }
333
+
334
+ /** Python `re.fullmatch(pattern, s)` — the whole string must match. */
335
+ function fullmatch(pattern: RegExp, s: string): RegExpExecArray | null {
336
+ const anchored = new RegExp(`^(?:${pattern.source})$`, pattern.flags.replace('g', ''));
337
+ return anchored.exec(s);
338
+ }
339
+
340
+ /**
341
+ * Python `repr(s)` for a string — used in the `unsupported when` error.
342
+ * CPython prefers single quotes, switching to double only when the string
343
+ * contains a single quote and no double quote.
344
+ */
345
+ function pyRepr(s: string): string {
346
+ const hasSingle = s.includes("'");
347
+ const hasDouble = s.includes('"');
348
+ const quote = hasSingle && !hasDouble ? '"' : "'";
349
+ let body = '';
350
+ for (const ch of s) {
351
+ if (ch === '\\') {
352
+ body += '\\\\';
353
+ } else if (ch === quote) {
354
+ body += `\\${ch}`;
355
+ } else if (ch === '\n') {
356
+ body += '\\n';
357
+ } else if (ch === '\r') {
358
+ body += '\\r';
359
+ } else if (ch === '\t') {
360
+ body += '\\t';
361
+ } else {
362
+ body += ch;
363
+ }
364
+ }
365
+ return `${quote}${body}${quote}`;
366
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Persona policy — behavioural parameters resolved from `state.persona`.
3
+ *
4
+ * TypeScript twin of `work_engine/persona_policy.py` (ADR-200 py2ts Phase 1 —
5
+ * work_engine foundation). Public API names stay snake_case to mirror the
6
+ * Python module 1:1 (per ADR-200 — Python style is part of the contract).
7
+ *
8
+ * Three personas ship today, each keyed by the string already carried
9
+ * on `DeliveryState.persona` (see
10
+ * `docs/contracts/implement-ticket-flow.md#personas`):
11
+ *
12
+ * `senior-engineer`
13
+ * Default. Runs every step. No test widening.
14
+ * `qa`
15
+ * Runs every step but widens the `test` scope to the full suite
16
+ * (`scope=full` in the `run-tests` directive) so regressions
17
+ * outside the changed paths are caught.
18
+ * `advisory`
19
+ * Plan-only mode. `implement`, `test`, and `verify` short-
20
+ * circuit to SUCCESS without doing work — the flow produces a
21
+ * delivery report whose value is the plan itself, and next-command
22
+ * suggestions are suppressed (nothing was committed).
23
+ *
24
+ * Unknown persona names fall back to `senior-engineer` rather than
25
+ * raising, so a mistyped value never aborts a run mid-flight. The
26
+ * caller is responsible for validating the string at the state-
27
+ * construction boundary if strict behaviour is needed.
28
+ */
29
+
30
+ /** Name used when `state.persona` is empty or unrecognised. */
31
+ export const DEFAULT_PERSONA = 'senior-engineer';
32
+
33
+ /**
34
+ * Behavioural flags read by individual step handlers.
35
+ *
36
+ * The Python source is a frozen dataclass; this class mirrors its field
37
+ * order and default values. Instances are treated as read-only configuration
38
+ * (the Python `frozen=True` contract) — `_POLICIES` holds the only instances
39
+ * and callers never mutate them.
40
+ */
41
+ export class PersonaPolicy {
42
+ readonly name: string;
43
+ readonly allows_implement: boolean;
44
+ readonly allows_test: boolean;
45
+ readonly allows_verify: boolean;
46
+ readonly widen_tests: boolean;
47
+ readonly suggests_next_commands: boolean;
48
+
49
+ constructor(args: {
50
+ name: string;
51
+ allows_implement?: boolean;
52
+ allows_test?: boolean;
53
+ allows_verify?: boolean;
54
+ widen_tests?: boolean;
55
+ suggests_next_commands?: boolean;
56
+ }) {
57
+ this.name = args.name;
58
+ this.allows_implement = args.allows_implement ?? true;
59
+ this.allows_test = args.allows_test ?? true;
60
+ this.allows_verify = args.allows_verify ?? true;
61
+ this.widen_tests = args.widen_tests ?? false;
62
+ this.suggests_next_commands = args.suggests_next_commands ?? true;
63
+ Object.freeze(this);
64
+ }
65
+ }
66
+
67
+ const _POLICIES: Record<string, PersonaPolicy> = {
68
+ 'senior-engineer': new PersonaPolicy({ name: 'senior-engineer' }),
69
+ qa: new PersonaPolicy({ name: 'qa', widen_tests: true }),
70
+ advisory: new PersonaPolicy({
71
+ name: 'advisory',
72
+ allows_implement: false,
73
+ allows_test: false,
74
+ allows_verify: false,
75
+ suggests_next_commands: false,
76
+ }),
77
+ };
78
+
79
+ /**
80
+ * Return the policy for `persona`; fall back to the default on miss.
81
+ *
82
+ * `persona` is typed `unknown` because it originates from
83
+ * `DeliveryState.persona` which the Python dataclass declares as `str`
84
+ * but does not enforce — a caller may set it to `None`/`null` while
85
+ * wiring up a partial state in tests.
86
+ */
87
+ export function resolve_policy(persona: unknown): PersonaPolicy {
88
+ if (typeof persona === 'string' && persona in _POLICIES) {
89
+ return _POLICIES[persona] as PersonaPolicy;
90
+ }
91
+ return _POLICIES[DEFAULT_PERSONA] as PersonaPolicy;
92
+ }
93
+
94
+ /** Return the persona names shipped today, in insertion order. */
95
+ export function known_personas(): string[] {
96
+ return Object.keys(_POLICIES);
97
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Diff resolver — wrap a unified-diff payload as an {@link Input} envelope.
3
+ *
4
+ * TypeScript twin of `work_engine/resolvers/diff.py` (ADR-200 py2ts —
5
+ * work_engine foundation). Public API names stay snake_case to mirror the
6
+ * Python module 1:1 (per ADR-200 — Python style is part of the contract).
7
+ *
8
+ * The resolver is the R3 Phase 1 entry point for the "improve this screen via
9
+ * diff/PR" surface: a user (or `/work` adapter) hands the engine a patch text,
10
+ * the resolver normalises it, and the dispatcher routes it through the UI-track
11
+ * `ui-improve` directive set.
12
+ *
13
+ * Like `work_engine.resolvers.prompt`, this module is intentionally thin:
14
+ * it normalises and rejects garbage payloads, nothing more. Reconstruction of
15
+ * acceptance criteria + assumptions + confidence is the job of the
16
+ * `refine-prompt` skill (R2 Phase 3) running against the diff once the engine
17
+ * hits the `refine` step. Keeping the split sharp means the envelope shape
18
+ * stays cheap to round-trip through state and the heavy lifting stays with the
19
+ * agent-directive halt where it belongs.
20
+ *
21
+ * The envelope mirrors the prompt resolver's shape so a single refiner code
22
+ * path can read both — `{raw, reconstructed_ac, assumptions}`. The only
23
+ * material difference is the heuristic header check that rejects payloads
24
+ * that obviously are not unified-diff text.
25
+ */
26
+ import { Input } from '../state.js';
27
+
28
+ export const KIND = 'diff';
29
+ /** Wire value carried in {@link Input.kind}. */
30
+
31
+ const _MIN_DIFF_LEN = 1;
32
+ /**
33
+ * Minimum non-whitespace character count before the heuristic runs.
34
+ *
35
+ * The resolver is not a *quality* gate; it only rejects literally empty
36
+ * payloads and obvious non-diffs. A semantically empty diff (e.g., headers but
37
+ * no hunks) is still accepted so the refiner can score its tractability and
38
+ * surface a `low`-band halt where appropriate.
39
+ */
40
+
41
+ const _DIFF_MARKERS: readonly RegExp[] = [
42
+ /^diff --git /m,
43
+ /^--- /m,
44
+ /^\+\+\+ /m,
45
+ /^@@ /m,
46
+ /^Index: /m,
47
+ ];
48
+ /**
49
+ * Heuristic markers that flag a payload as a unified or git-style diff.
50
+ *
51
+ * A payload qualifies if **any** marker matches — the resolver accepts unified
52
+ * diffs (`--- `/`+++ `/`@@ `), `git diff` output (`diff --git`), and
53
+ * the legacy `Index: ` SVN/CVS header. The match is anchored at line start so
54
+ * quoted snippets inside prose ("the function `--- foo` failed") do not pass
55
+ * the gate.
56
+ */
57
+
58
+ /** Raised when a payload cannot be resolved into a diff envelope. */
59
+ export class DiffResolverError extends Error {
60
+ constructor(message: string) {
61
+ super(message);
62
+ this.name = 'DiffResolverError';
63
+ Object.setPrototypeOf(this, DiffResolverError.prototype);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Return an {@link Input} carrying the raw diff + empty refinement slots.
69
+ *
70
+ * @param raw The user-supplied diff text. Whitespace is preserved verbatim —
71
+ * the refiner reads original spacing/casing when scoring goal clarity, and
72
+ * a unified-diff round-trip cannot tolerate normalised whitespace.
73
+ * @returns Envelope of shape
74
+ * `{"kind": "diff", "data": {"raw": <raw>, "reconstructed_ac": [],
75
+ * "assumptions": []}}`. The two empty lists are placeholders the
76
+ * `refine-prompt` skill writes into on the rebound from `refine`.
77
+ * @throws {@link DiffResolverError} If `raw` is not a string, contains no
78
+ * non-whitespace characters, or does not match any {@link _DIFF_MARKERS}.
79
+ * The marker check guards against accidentally routing free-form prose
80
+ * through the diff path.
81
+ */
82
+ export function build_envelope(raw: unknown): Input {
83
+ if (typeof raw !== 'string') {
84
+ throw new DiffResolverError(
85
+ `diff must be a string; got ${pyTypeName(raw)}`,
86
+ );
87
+ }
88
+ if (pyStrip(raw).length < _MIN_DIFF_LEN) {
89
+ throw new DiffResolverError(
90
+ 'diff is empty or whitespace-only — nothing to resolve',
91
+ );
92
+ }
93
+ if (!_DIFF_MARKERS.some((marker) => reSearch(marker, raw))) {
94
+ throw new DiffResolverError(
95
+ 'payload does not look like a unified diff — expected one of ' +
96
+ "'diff --git', '--- ', '+++ ', '@@ ', or 'Index: ' headers",
97
+ );
98
+ }
99
+ return new Input(KIND, {
100
+ raw: raw,
101
+ reconstructed_ac: [],
102
+ assumptions: [],
103
+ });
104
+ }
105
+
106
+ // ── Python-parity helpers ───────────────────────────────────────────────
107
+
108
+ /**
109
+ * Mirror Python `re.Pattern.search` for a boolean hit. The patterns carry the
110
+ * `m` (MULTILINE) flag so `^` matches at every line start, identical to
111
+ * `re.compile(..., re.MULTILINE)`. The regexes are flag-only (no `g`), so a
112
+ * fresh `.test` has no `lastIndex` state to reset between calls.
113
+ */
114
+ function reSearch(pattern: RegExp, text: string): boolean {
115
+ return pattern.test(text);
116
+ }
117
+
118
+ /**
119
+ * Mirror Python `str.strip()` — see `resolvers/prompt.ts` for the parity note.
120
+ * `String.trim()` is faithful for every realistic whitespace-only payload.
121
+ */
122
+ function pyStrip(s: string): string {
123
+ return s.trim();
124
+ }
125
+
126
+ /** Python `type(x).__name__` for the non-string `isinstance` guard branch. */
127
+ function pyTypeName(v: unknown): string {
128
+ if (v === null) return 'NoneType';
129
+ if (v === undefined) return 'NoneType';
130
+ if (typeof v === 'boolean') return 'bool';
131
+ if (typeof v === 'string') return 'str';
132
+ if (typeof v === 'number') return Number.isInteger(v) ? 'int' : 'float';
133
+ if (Array.isArray(v)) return 'list';
134
+ return 'dict';
135
+ }