@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,161 @@
1
+ /**
2
+ * `MemoryVisibilityHook` — emit the visibility line on save.
3
+ *
4
+ * TypeScript twin of `work_engine/hooks/builtin/memory_visibility.py`
5
+ * (ADR-200 py2ts — work_engine.hooks.builtin subpackage). Implements the
6
+ * producer side of `docs/contracts/memory-visibility-v1.md`: derive
7
+ * `asks/hits/ids` from `state.memory` and thread the rendered line into
8
+ * `state.report`.
9
+ *
10
+ * Fires on `before_save`. Default-off; opt-in via `.agent-settings.yml`. The
11
+ * hook is purely observational: failures surface as {@link HookError}
12
+ * (non-fatal per the three-tier contract).
13
+ */
14
+ import { summarise_memory, summarise_verify } from '../../scoring/decision_trace.js';
15
+ import {
16
+ DEFAULT_ASKED_TYPES,
17
+ compute_affected,
18
+ format_changed_decisions_block,
19
+ format_line,
20
+ should_emit,
21
+ summarise_visibility,
22
+ } from '../../scoring/memory_visibility.js';
23
+ import type { HookContext } from '../context.js';
24
+ import { HookEvent } from '../events.js';
25
+ import { HookError } from '../exceptions.js';
26
+ import type { HookRegistry } from '../registry.js';
27
+
28
+ /** Arbitrary value, mirroring the Python `Any` fields. */
29
+ type Any = unknown;
30
+
31
+ /**
32
+ * Thread the `🧠 Memory: <hits>/<asks> · ids=[…]` line into the report.
33
+ *
34
+ * `memory_cadence` is the `memory.cadence` cadence key. `visibility_off`
35
+ * mirrors `memory.visibility: off`. `asked_types` overrides the list of
36
+ * memory types treated as `asks` in the visibility line.
37
+ */
38
+ export class MemoryVisibilityHook {
39
+ private readonly _memory_cadence: string;
40
+ private readonly _visibility_off: boolean;
41
+ private readonly _asked_types: readonly string[];
42
+
43
+ constructor(
44
+ options: {
45
+ memory_cadence?: string;
46
+ visibility_off?: boolean;
47
+ asked_types?: Iterable<string> | null;
48
+ } = {},
49
+ ) {
50
+ this._memory_cadence = options.memory_cadence ?? 'always';
51
+ this._visibility_off = options.visibility_off ?? false;
52
+ this._asked_types =
53
+ options.asked_types !== undefined && options.asked_types !== null
54
+ ? [...options.asked_types]
55
+ : DEFAULT_ASKED_TYPES;
56
+ }
57
+
58
+ /** Register the visibility-line emitter on `before_save`. */
59
+ register(registry: HookRegistry): void {
60
+ registry.register(HookEvent.BEFORE_SAVE, (ctx) => this._on_before_save(ctx));
61
+ }
62
+
63
+ private _on_before_save(ctx: HookContext): void {
64
+ const work = ctx.work;
65
+ if (work === null || work === undefined) {
66
+ return;
67
+ }
68
+ const memory = _getattr(work, 'memory', null);
69
+ const summary = summarise_visibility(memory, { asked_types: this._asked_types });
70
+ if (
71
+ !should_emit(summary, {
72
+ memory_cadence: this._memory_cadence,
73
+ visibility_off: this._visibility_off,
74
+ })
75
+ ) {
76
+ return;
77
+ }
78
+ const affected = this._derive_affected(work, memory);
79
+ const line = format_line(summary, { affected });
80
+ if (!line) {
81
+ return;
82
+ }
83
+ const block = format_changed_decisions_block(
84
+ (summary['ids'] as string[]) || [],
85
+ affected,
86
+ );
87
+ const existing = (_getattr(work, 'report', '') as string) || '';
88
+ const rendered = block === null ? line : `${line}\n\n${block}`;
89
+ if (existing.includes(line) && (block === null || existing.includes(block))) {
90
+ return;
91
+ }
92
+ const sep = existing ? '\n\n' : '';
93
+ try {
94
+ (work as Record<string, Any>)['report'] = `${existing}${sep}${rendered}`;
95
+ } catch (_exc) {
96
+ throw new HookError('memory-visibility: state.report not writable');
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Compute the closed-list `affected` keys for this work step. Returns
102
+ * `null` when memory was not consulted (hits == 0).
103
+ */
104
+ private _derive_affected(work: Any, memory: Any): string[] | null {
105
+ const memory_summary = summarise_memory(memory);
106
+ const verify_summary = summarise_verify(_getattr(work, 'verify', null));
107
+ const ambiguity = _pyTruthy(_getattr(work, 'questions', null));
108
+ return compute_affected({
109
+ memory_hits: memory_summary['hits'] as number,
110
+ verify_claims: verify_summary['claims'] as number,
111
+ verify_first_try_passes: verify_summary['first_try_passes'] as number,
112
+ ambiguity_flag: ambiguity,
113
+ changes: _getattr(work, 'changes', null),
114
+ applied_rules: _getattr(work, 'applied_rules', null) as string[] | null,
115
+ test_plan: _getattr(work, 'test_plan', null) as string[] | null,
116
+ });
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Convenience helper: render the line directly from a memory list. Used by
122
+ * external callers that have a `memory` list but no {@link HookContext}.
123
+ * Returns `null` when `asks == 0`.
124
+ */
125
+ export function derive_visibility(memory: Any): string | null {
126
+ return format_line(summarise_visibility(memory));
127
+ }
128
+
129
+ /** Python `getattr(obj, name, default)`. */
130
+ function _getattr(obj: Any, name: string, dflt: Any): Any {
131
+ if (obj !== null && typeof obj === 'object' && name in (obj as object)) {
132
+ return (obj as Record<string, Any>)[name];
133
+ }
134
+ return dflt;
135
+ }
136
+
137
+ /** Python `bool(x)` truthiness for the `questions` shapes seen here. */
138
+ function _pyTruthy(value: Any): boolean {
139
+ if (value === null || value === undefined || value === false) {
140
+ return false;
141
+ }
142
+ if (value === true) {
143
+ return true;
144
+ }
145
+ if (typeof value === 'number') {
146
+ return value !== 0;
147
+ }
148
+ if (typeof value === 'string') {
149
+ return value.length > 0;
150
+ }
151
+ if (Array.isArray(value)) {
152
+ return value.length > 0;
153
+ }
154
+ if (value instanceof Map || value instanceof Set) {
155
+ return value.size > 0;
156
+ }
157
+ if (typeof value === 'object') {
158
+ return Object.keys(value as Record<string, unknown>).length > 0;
159
+ }
160
+ return true;
161
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `StateShapeValidationHook` — round-trip the v1 envelope on load and save.
3
+ *
4
+ * TypeScript twin of `work_engine/hooks/builtin/state_shape_validation.py`
5
+ * (ADR-200 py2ts — work_engine.hooks.builtin subpackage). Fires on
6
+ * `AFTER_LOAD` and `BEFORE_SAVE`. For each event, serialises the live
7
+ * `WorkState` through `state.to_dict` and re-validates via `state.from_dict`.
8
+ * A `SchemaError` from either side is reported as a {@link HookError} so the
9
+ * runner warns and continues — observability, not a gate.
10
+ */
11
+ import { SchemaError, type WorkState, from_dict, to_dict } from '../../state.js';
12
+ import type { HookContext } from '../context.js';
13
+ import { HookEvent } from '../events.js';
14
+ import { HookError } from '../exceptions.js';
15
+ import type { HookRegistry } from '../registry.js';
16
+
17
+ /** Round-trips the loaded `WorkState` against the v1 schema. */
18
+ export class StateShapeValidationHook {
19
+ /** Register on AFTER_LOAD and BEFORE_SAVE. */
20
+ register(registry: HookRegistry): void {
21
+ registry.register(HookEvent.AFTER_LOAD, (ctx) => this._validate(ctx));
22
+ registry.register(HookEvent.BEFORE_SAVE, (ctx) => this._validate(ctx));
23
+ }
24
+
25
+ private _validate(ctx: HookContext): void {
26
+ const work = ctx.work;
27
+ if (work === null || work === undefined) {
28
+ // Should not happen on AFTER_LOAD/BEFORE_SAVE; treat as a
29
+ // hook-side bug rather than swallow silently.
30
+ throw new HookError(
31
+ 'state-shape validation: HookContext.work is None at ' +
32
+ `event for state_file=${ctx.state_file === null ? 'None' : ctx.state_file}`,
33
+ );
34
+ }
35
+
36
+ try {
37
+ from_dict(to_dict(work as WorkState));
38
+ } catch (exc) {
39
+ if (exc instanceof SchemaError) {
40
+ throw new HookError(`state-shape validation failed: ${exc.message}`);
41
+ }
42
+ throw exc;
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * `TraceHook` — emit one stderr line per hook event.
3
+ *
4
+ * TypeScript twin of `work_engine/hooks/builtin/trace.py` (ADR-200 py2ts —
5
+ * work_engine.hooks.builtin subpackage). Registers on every `HookEvent`;
6
+ * output goes to a configurable stream (default `process.stderr`) so tests can
7
+ * capture it.
8
+ *
9
+ * Pure observability — never mutates context, never halts. A misbehaving sink
10
+ * raises {@link HookError}, which the runner swallows with a warning.
11
+ */
12
+ import type { HookEvent } from '../events.js';
13
+ import { HOOK_EVENTS } from '../events.js';
14
+ import type { HookContext } from '../context.js';
15
+ import { HookError } from '../exceptions.js';
16
+ import type { HookCallback, HookRegistry } from '../registry.js';
17
+
18
+ /** Arbitrary value, mirroring the Python `Any` fields. */
19
+ type Any = unknown;
20
+
21
+ /**
22
+ * Minimal write+flush sink, mirroring Python's `IO[str]`. `process.stderr`
23
+ * satisfies it; tests pass a string-collector.
24
+ */
25
+ export interface TextStream {
26
+ write(s: string): unknown;
27
+ flush?(): void;
28
+ }
29
+
30
+ /** Stderr-trace hook for every lifecycle event. */
31
+ export class TraceHook {
32
+ private readonly _stream: TextStream;
33
+ private readonly _prefix: string;
34
+
35
+ constructor(stream: TextStream | null = null, prefix = '[hook]') {
36
+ this._stream = stream !== null ? stream : process.stderr;
37
+ this._prefix = prefix;
38
+ }
39
+
40
+ /** Register the trace callback for every `HookEvent`. */
41
+ register(registry: HookRegistry): void {
42
+ for (const event of HOOK_EVENTS) {
43
+ registry.register(event, this._make_callback(event));
44
+ }
45
+ }
46
+
47
+ private _make_callback(event: HookEvent): HookCallback {
48
+ const _cb = (ctx: HookContext): void => {
49
+ try {
50
+ const line = this._format(event, ctx);
51
+ this._stream.write(line + '\n');
52
+ if (typeof this._stream.flush === 'function') {
53
+ this._stream.flush();
54
+ }
55
+ } catch (exc) {
56
+ if (exc instanceof HookError) {
57
+ throw exc;
58
+ }
59
+ // Mirror Python `except (OSError, ValueError)`: wrap stream
60
+ // failures as a non-fatal HookError; re-raise anything else.
61
+ throw new HookError(`trace stream unavailable: ${_errStr(exc)}`);
62
+ }
63
+ };
64
+ return _cb;
65
+ }
66
+
67
+ /**
68
+ * Build a one-line trace record.
69
+ *
70
+ * Format: `[hook] event=<name> step=<step> set=<set> outcome=<o>`.
71
+ * Missing fields are skipped so the line stays short on events that only
72
+ * carry a subset of the context.
73
+ */
74
+ private _format(event: HookEvent, ctx: HookContext): string {
75
+ const parts: string[] = [this._prefix, `event=${event}`];
76
+ if (ctx.step_name) {
77
+ parts.push(`step=${ctx.step_name}`);
78
+ }
79
+ if (ctx.set_name) {
80
+ parts.push(`set=${ctx.set_name}`);
81
+ }
82
+ if (ctx.result !== null && ctx.result !== undefined) {
83
+ const outcome = _getattr(ctx.result, 'outcome', null);
84
+ if (outcome !== null && outcome !== undefined) {
85
+ parts.push(`outcome=${_valueOf(outcome)}`);
86
+ }
87
+ }
88
+ if (ctx.final !== null && ctx.final !== undefined) {
89
+ parts.push(`final=${_valueOf(ctx.final)}`);
90
+ }
91
+ if (ctx.halting) {
92
+ parts.push(`halting=${ctx.halting}`);
93
+ }
94
+ if (ctx.exception !== null && ctx.exception !== undefined) {
95
+ parts.push(`exception=${_typeName(ctx.exception)}`);
96
+ }
97
+ return parts.join(' ');
98
+ }
99
+ }
100
+
101
+ /** Python `getattr(obj, name, default)`. */
102
+ function _getattr(obj: Any, name: string, dflt: Any): Any {
103
+ if (obj !== null && typeof obj === 'object' && name in (obj as object)) {
104
+ return (obj as Record<string, Any>)[name];
105
+ }
106
+ return dflt;
107
+ }
108
+
109
+ /** Python `getattr(x, "value", x)` — enum value if present, else the value. */
110
+ function _valueOf(x: Any): string {
111
+ if (x !== null && typeof x === 'object' && 'value' in (x as object)) {
112
+ return String((x as Record<string, Any>)['value']);
113
+ }
114
+ return String(x);
115
+ }
116
+
117
+ /** Python `type(exc).__name__`. */
118
+ function _typeName(exc: Any): string {
119
+ if (exc instanceof Error) {
120
+ return exc.constructor.name;
121
+ }
122
+ if (exc !== null && exc !== undefined && typeof exc === 'object') {
123
+ return (exc as { constructor?: { name?: string } }).constructor?.name ?? 'object';
124
+ }
125
+ return typeof exc;
126
+ }
127
+
128
+ /** `str(exc)` for the wrapped-sink-failure message. */
129
+ function _errStr(exc: unknown): string {
130
+ if (exc instanceof Error) {
131
+ return exc.message;
132
+ }
133
+ return String(exc);
134
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * `HookContext` — payload carried into every hook callback.
3
+ *
4
+ * TypeScript twin of `work_engine/hooks/context.py` (ADR-200 py2ts —
5
+ * work_engine.hooks subpackage). One class for both layers. Most fields are
6
+ * `null` for any given event; the per-event subset is documented below and
7
+ * locked by the roadmap's hook event surface table. Hooks must tolerate
8
+ * missing fields gracefully.
9
+ *
10
+ * Per-event subset (mirrors the roadmap):
11
+ *
12
+ * Dispatcher layer (`delivery` is set; `work` is `null`):
13
+ * - `before_step` → `step_name`, `delivery`
14
+ * - `after_step` → `step_name`, `delivery`, `result`
15
+ * - `on_halt` → `step_name`, `delivery`, `result`
16
+ * - `on_error` → `step_name`, `delivery`, `exception`
17
+ *
18
+ * CLI layer (`work` is set; `delivery` may be set after load):
19
+ * - `before_load` → `state_file`, `args`
20
+ * - `after_load` → `state_file`, `work`, `fmt`
21
+ * - `before_dispatch` → `work`, `delivery`, `set_name`
22
+ * - `after_dispatch` → `work`, `delivery`, `final`, `halting`
23
+ * - `before_save` → `work`, `delivery`, `fmt`
24
+ * - `after_save` → `work`, `state_file`, `fmt`
25
+ */
26
+
27
+ /** Arbitrary value, mirroring the Python `Any` fields. */
28
+ export type Any = unknown;
29
+
30
+ /** Per-event payload passed to every hook callback (matches the dataclass). */
31
+ export interface HookContextInit {
32
+ // Dispatcher-layer refs.
33
+ step_name?: string | null;
34
+ delivery?: Any; // DeliveryState
35
+ result?: Any; // StepResult
36
+ exception?: Any; // BaseException
37
+
38
+ // CLI-layer refs.
39
+ work?: Any; // WorkState
40
+ state_file?: string | null;
41
+ fmt?: string | null;
42
+ set_name?: string | null;
43
+ final?: Any; // Outcome
44
+ halting?: string | null;
45
+ args?: Any; // argparse.Namespace
46
+
47
+ // Escape hatch for hook-specific state.
48
+ extra?: Record<string, Any>;
49
+ }
50
+
51
+ /**
52
+ * Per-event payload passed to every hook callback.
53
+ *
54
+ * Fields are intentionally optional — the runner does not validate which
55
+ * ones are populated for a given event. The contract is enforced by the
56
+ * call sites, not by the class.
57
+ *
58
+ * `extra` exists as an escape hatch for hook-specific state that does not
59
+ * warrant a dedicated field.
60
+ */
61
+ export class HookContext {
62
+ // Dispatcher-layer refs.
63
+ step_name: string | null;
64
+ delivery: Any;
65
+ result: Any;
66
+ exception: Any;
67
+
68
+ // CLI-layer refs.
69
+ work: Any;
70
+ state_file: string | null;
71
+ fmt: string | null;
72
+ set_name: string | null;
73
+ final: Any;
74
+ halting: string | null;
75
+ args: Any;
76
+
77
+ // Escape hatch for hook-specific state.
78
+ extra: Record<string, Any>;
79
+
80
+ constructor(init: HookContextInit = {}) {
81
+ this.step_name = init.step_name ?? null;
82
+ this.delivery = init.delivery ?? null;
83
+ this.result = init.result ?? null;
84
+ this.exception = init.exception ?? null;
85
+ this.work = init.work ?? null;
86
+ this.state_file = init.state_file ?? null;
87
+ this.fmt = init.fmt ?? null;
88
+ this.set_name = init.set_name ?? null;
89
+ this.final = init.final ?? null;
90
+ this.halting = init.halting ?? null;
91
+ this.args = init.args ?? null;
92
+ this.extra = init.extra ?? {};
93
+ }
94
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Hook event surface for `work_engine`.
3
+ *
4
+ * TypeScript twin of `work_engine/hooks/events.py` (ADR-200 py2ts —
5
+ * work_engine.hooks subpackage). Mirrors the Python `str`-Enum 1:1: each
6
+ * value is the event name verbatim, so round-trips stay trivial for
7
+ * telemetry and JSON tracing.
8
+ *
9
+ * Ten events split across two layers per
10
+ * `agents/roadmaps/road-to-work-engine-hooks.md` (locked).
11
+ *
12
+ * Dispatcher-layer events fire from inside `dispatcher.dispatch()` and
13
+ * operate on `DeliveryState` (legacy, internal). CLI-layer events fire
14
+ * from `cli.main()` and operate on `WorkState` (v1 envelope) plus
15
+ * auxiliary refs (`state_file`, `fmt`, `args`).
16
+ */
17
+
18
+ /**
19
+ * Lifecycle events emitted by the work engine.
20
+ *
21
+ * Modelled as a const object + value-union so it behaves like the Python
22
+ * `str`-Enum: members compare equal to their string value, and the value
23
+ * IS the event name. `HookEvent.BEFORE_STEP === 'before_step'`.
24
+ */
25
+ export const HookEvent = {
26
+ // Dispatcher layer (DeliveryState).
27
+ BEFORE_STEP: 'before_step',
28
+ AFTER_STEP: 'after_step',
29
+ ON_HALT: 'on_halt',
30
+ ON_ERROR: 'on_error',
31
+
32
+ // CLI layer (WorkState).
33
+ BEFORE_LOAD: 'before_load',
34
+ AFTER_LOAD: 'after_load',
35
+ BEFORE_DISPATCH: 'before_dispatch',
36
+ AFTER_DISPATCH: 'after_dispatch',
37
+ BEFORE_SAVE: 'before_save',
38
+ AFTER_SAVE: 'after_save',
39
+ } as const;
40
+
41
+ export type HookEvent = (typeof HookEvent)[keyof typeof HookEvent];
42
+
43
+ /**
44
+ * Iteration order over every event, mirroring Python `for event in HookEvent`
45
+ * (declaration order). Used by {@link TraceHook} to register on all events.
46
+ */
47
+ export const HOOK_EVENTS: readonly HookEvent[] = [
48
+ HookEvent.BEFORE_STEP,
49
+ HookEvent.AFTER_STEP,
50
+ HookEvent.ON_HALT,
51
+ HookEvent.ON_ERROR,
52
+ HookEvent.BEFORE_LOAD,
53
+ HookEvent.AFTER_LOAD,
54
+ HookEvent.BEFORE_DISPATCH,
55
+ HookEvent.AFTER_DISPATCH,
56
+ HookEvent.BEFORE_SAVE,
57
+ HookEvent.AFTER_SAVE,
58
+ ];
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Hook control-flow signals.
3
+ *
4
+ * TypeScript twin of `work_engine/hooks/exceptions.py` (ADR-200 py2ts —
5
+ * work_engine.hooks subpackage). Class names stay 1:1 with the Python module.
6
+ *
7
+ * Three-tier error contract (locked by roadmap P1):
8
+ *
9
+ * - `HookError` — non-fatal. Hook implementation failed; the runner
10
+ * catches it, warns, and continues with the next callback for the same
11
+ * event. Work proceeds.
12
+ * - `HookHalt` — fatal-controlled. Hook demands a clean stop (canonical
13
+ * example: chat-history `turn-check` foreign session). The runner
14
+ * catches it and **returns** it to the caller, who decides how to
15
+ * surface it (engine halt, CLI exit code 2 + readable surface). Not
16
+ * re-raised through the dispatch loop.
17
+ * - any other `Error` — fatal-uncontrolled. Treated as a bug in the
18
+ * hook. The runner lets it propagate verbatim; dispatch unwinds.
19
+ *
20
+ * Both signals share a private `_HookSignal` base so the runner can
21
+ * distinguish hook-originated control flow from genuine bugs.
22
+ */
23
+
24
+ /**
25
+ * Internal marker for hook-originated control flow.
26
+ *
27
+ * Not part of the public API. The runner uses `instanceof` checks
28
+ * against the concrete subclasses below; the base exists only so a
29
+ * single `instanceof _HookSignal` would cover both signals if a future
30
+ * refactor needs it.
31
+ */
32
+ export class _HookSignal extends Error {
33
+ constructor(message?: string) {
34
+ super(message);
35
+ Object.setPrototypeOf(this, _HookSignal.prototype);
36
+ this.name = '_HookSignal';
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Non-fatal hook failure.
42
+ *
43
+ * Raised when a hook callback fails in a way the *engine* should ignore.
44
+ * The runner catches it, emits a warning with the message, and moves
45
+ * on to the next callback registered for the event.
46
+ *
47
+ * Use this for transient or non-critical hook failures (telemetry
48
+ * sinks, optional reporters). Do **not** use it to signal "stop the
49
+ * engine" — that is what {@link HookHalt} is for.
50
+ */
51
+ export class HookError extends _HookSignal {
52
+ constructor(message?: string) {
53
+ super(message);
54
+ Object.setPrototypeOf(this, HookError.prototype);
55
+ this.name = 'HookError';
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Fatal-controlled stop requested by a hook.
61
+ *
62
+ * Hooks raise this when execution must not continue. The runner catches
63
+ * it and returns it to the caller; the caller turns it into the
64
+ * appropriate halt surface (dispatcher `Outcome.BLOCKED`, CLI exit 2).
65
+ *
66
+ * `surface` is a list of pre-formatted numbered options per the
67
+ * `user-interaction` rule (one entry per line). Callers must not
68
+ * reformat — surface is rendered verbatim.
69
+ *
70
+ * `reason` is a short machine-readable code (e.g. `"foreign"`,
71
+ * `"missing"`, `"validation_failed"`) for logging and tests; it is not
72
+ * shown to the user.
73
+ */
74
+ export class HookHalt extends _HookSignal {
75
+ readonly reason: string;
76
+ readonly surface: string[];
77
+
78
+ constructor(reason: string, surface: string[] | null = null) {
79
+ super(reason);
80
+ Object.setPrototypeOf(this, HookHalt.prototype);
81
+ this.name = 'HookHalt';
82
+ this.reason = reason;
83
+ this.surface = surface ? [...surface] : [];
84
+ }
85
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `work_engine.hooks` — cross-cutting lifecycle hooks for the engine.
3
+ *
4
+ * TypeScript twin of `work_engine/hooks/__init__.py` (ADR-200 py2ts —
5
+ * work_engine.hooks subpackage). Public surface:
6
+ *
7
+ * - `HookEvent` — ten lifecycle events, two layers.
8
+ * - `HookContext` — per-event payload.
9
+ * - `HookError` / `HookHalt` — three-tier error contract.
10
+ * - `HookRegistry` — insertion-ordered event → callbacks map.
11
+ * - `HookRunner` — single emit point, owns the error contract.
12
+ */
13
+ export {
14
+ ChatHistoryAppendHook,
15
+ ChatHistoryHaltAppendHook,
16
+ DecisionTraceHook,
17
+ DirectiveSetGuardHook,
18
+ HaltSurfaceAuditHook,
19
+ MemoryVisibilityHook,
20
+ StateShapeValidationHook,
21
+ TraceHook,
22
+ } from './builtin/index.js';
23
+ export { HookContext } from './context.js';
24
+ export { HookEvent } from './events.js';
25
+ export { HookError, HookHalt } from './exceptions.js';
26
+ export { type HookCallback, HookRegistry } from './registry.js';
27
+ export { HookRunner } from './runner.js';
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `HookRegistry` — insertion-ordered map from event to callbacks.
3
+ *
4
+ * TypeScript twin of `work_engine/hooks/registry.py` (ADR-200 py2ts —
5
+ * work_engine.hooks subpackage). Phase 1 ships insertion-ordered
6
+ * registration only.
7
+ *
8
+ * The registry is a plain container. It does not invoke callbacks, does
9
+ * not catch exceptions, and does not know about the error contract; that
10
+ * responsibility lives in {@link HookRunner}.
11
+ */
12
+ import type { HookContext } from './context.js';
13
+ import type { HookEvent } from './events.js';
14
+
15
+ /**
16
+ * A hook callback. Returns `void` on success, throws `HookError` or
17
+ * `HookHalt` to signal control flow per `exceptions.ts`.
18
+ */
19
+ export type HookCallback = (ctx: HookContext) => void;
20
+
21
+ /**
22
+ * Insertion-ordered registry of hook callbacks per event.
23
+ *
24
+ * Single instance per CLI invocation. Built once and shared with
25
+ * `dispatch()` so dispatcher events and CLI events are routed through the
26
+ * same callback set.
27
+ */
28
+ export class HookRegistry {
29
+ // Map preserves insertion order, mirroring Python's dict ordering.
30
+ private _hooks: Map<HookEvent, HookCallback[]> = new Map();
31
+
32
+ /**
33
+ * Register `callback` for `event`.
34
+ *
35
+ * Multiple callbacks for the same event are allowed; they fire in
36
+ * registration order.
37
+ */
38
+ register(event: HookEvent, callback: HookCallback): void {
39
+ let list = this._hooks.get(event);
40
+ if (list === undefined) {
41
+ list = [];
42
+ this._hooks.set(event, list);
43
+ }
44
+ list.push(callback);
45
+ }
46
+
47
+ /**
48
+ * Return callbacks registered for `event` in insertion order.
49
+ *
50
+ * Returns an empty array when no callbacks are registered — the runner
51
+ * uses this to short-circuit a no-op fast path.
52
+ */
53
+ for_event(event: HookEvent): HookCallback[] {
54
+ const list = this._hooks.get(event);
55
+ return list === undefined ? [] : [...list];
56
+ }
57
+
58
+ /**
59
+ * Iterate over events that have at least one callback.
60
+ *
61
+ * Diagnostics-only; not used on the hot path.
62
+ */
63
+ events(): IterableIterator<HookEvent> {
64
+ return this._hooks.keys();
65
+ }
66
+ }