@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,249 @@
1
+ /**
2
+ * Argument parser and state-file constants for the CLI entry point.
3
+ *
4
+ * TypeScript twin of `work_engine/cli_args.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
+ * Extracted from `cli.py` in P2.3 of `road-to-post-pr29-optimize.md`.
9
+ * Behaviour-preserving: the parser shape, default values, and exit-code
10
+ * semantics are identical to the pre-split version. The constants moved here
11
+ * so the parser default and the legacy-file detector both reference a single
12
+ * source of truth.
13
+ *
14
+ * The Python source returns an `argparse.ArgumentParser`; this twin exposes
15
+ * {@link parse_args} instead, mirroring argparse's runtime behaviour for the
16
+ * declared flags: long-option prefix abbreviation, `--flag=value` /
17
+ * `--flag value` forms, `store_true` flags, `-h`/`--help` exit-0, and exit-2
18
+ * on every error path (unknown flag, ambiguous abbreviation, missing value,
19
+ * explicit value on a store_true flag). `--help` prose is intentionally NOT
20
+ * a parity surface (ADR-200 — argparse help text is not byte-compared).
21
+ */
22
+
23
+ // Paths are kept as plain strings here (the Python source wraps them in
24
+ // `pathlib.Path` via `type=Path`; the consuming modules — state_io,
25
+ // input_builders — land in a later py2ts phase and own the Path semantics).
26
+ export const DEFAULT_STATE_FILE = '.work-state.json';
27
+ /**
28
+ * State file used when `--state-file` is not passed.
29
+ *
30
+ * Renamed from `.implement-ticket-state.json` in 1.15.0 alongside the
31
+ * `implement_ticket → work_engine` package move. The legacy filename is
32
+ * still recognised on load (see {@link LEGACY_STATE_FILE} below) so that
33
+ * existing checkouts surface a clear migration message instead of a
34
+ * silent "no state file" error.
35
+ */
36
+
37
+ export const LEGACY_STATE_FILE = '.implement-ticket-state.json';
38
+ /**
39
+ * Pre-1.15.0 default state file. Detected only as a migration hint;
40
+ * never written to. See `docs/MIGRATION.md`.
41
+ */
42
+
43
+ export const _FMT_V0 = 'v0';
44
+ export const _FMT_V1 = 'v1';
45
+ /**
46
+ * Wire-format markers carried alongside the loaded `WorkState`.
47
+ *
48
+ * Format-preserving roundtrip: `_load` records which shape it parsed,
49
+ * `_save` rewrites in that same shape. v0 in → v0 out (Goldens stay
50
+ * byte-equal); v1 in → v1 out (future flows produced by the migration
51
+ * tool or a fresh v1 init keep their envelope fields).
52
+ */
53
+
54
+ /** Parsed namespace — mirrors the argparse `Namespace` attribute names. */
55
+ export interface ParsedArgs {
56
+ state_file: string;
57
+ ticket_file: string | null;
58
+ prompt_file: string | null;
59
+ diff_file: string | null;
60
+ file_file: string | null;
61
+ persona: string | null;
62
+ no_hooks: boolean;
63
+ hooks_config: string | null;
64
+ }
65
+
66
+ /** Thrown internally to mirror argparse's exit-2 error path. */
67
+ class ArgparseExit extends Error {
68
+ readonly code: number;
69
+ constructor(code: number) {
70
+ super(`argparse exit ${code}`);
71
+ Object.setPrototypeOf(this, ArgparseExit.prototype);
72
+ this.name = 'ArgparseExit';
73
+ this.code = code;
74
+ }
75
+ }
76
+
77
+ const PROG = 'implement-ticket';
78
+
79
+ // Declared optionals, in declaration order (drives prefix-abbreviation
80
+ // candidate ordering and the usage line, exactly as argparse builds it).
81
+ interface OptionSpec {
82
+ flag: string; // canonical long option, e.g. "--state-file"
83
+ dest: keyof ParsedArgs; // namespace attribute
84
+ takesValue: boolean; // false for store_true
85
+ }
86
+
87
+ const OPTIONS: OptionSpec[] = [
88
+ { flag: '--state-file', dest: 'state_file', takesValue: true },
89
+ { flag: '--ticket-file', dest: 'ticket_file', takesValue: true },
90
+ { flag: '--prompt-file', dest: 'prompt_file', takesValue: true },
91
+ { flag: '--diff-file', dest: 'diff_file', takesValue: true },
92
+ { flag: '--file-file', dest: 'file_file', takesValue: true },
93
+ { flag: '--persona', dest: 'persona', takesValue: true },
94
+ { flag: '--no-hooks', dest: 'no_hooks', takesValue: false },
95
+ { flag: '--hooks-config', dest: 'hooks_config', takesValue: true },
96
+ ];
97
+
98
+ // `-h` / `--help` is an implicit argparse optional; included as a prefix
99
+ // abbreviation candidate so e.g. `--he` resolves to `--help`.
100
+ const HELP_FLAGS = ['--help'];
101
+
102
+ function usage(): string {
103
+ // Compact one-line usage marker; the exact wrapping/prose is not a parity
104
+ // surface (ADR-200 — argparse usage/help text is not byte-compared).
105
+ return `usage: ${PROG} [-h] [options]`;
106
+ }
107
+
108
+ function err(message: string): never {
109
+ process.stderr.write(`${usage()}\n`);
110
+ process.stderr.write(`${PROG}: error: ${message}\n`);
111
+ throw new ArgparseExit(2);
112
+ }
113
+
114
+ /**
115
+ * Resolve a long option token to its canonical flag, applying argparse's
116
+ * unambiguous-prefix-abbreviation rule. Returns the matched spec, the
117
+ * sentinel `'help'`, or throws exit-2 on an ambiguous / unknown option.
118
+ */
119
+ function resolveLong(name: string): OptionSpec | 'help' {
120
+ // Exact match first (argparse short-circuits exact hits before abbrev).
121
+ const exact = OPTIONS.find((o) => o.flag === name);
122
+ if (exact) {
123
+ return exact;
124
+ }
125
+ if (HELP_FLAGS.includes(name)) {
126
+ return 'help';
127
+ }
128
+ // Prefix abbreviation across both the declared options and --help.
129
+ const candidates: Array<OptionSpec | 'help'> = [];
130
+ for (const o of OPTIONS) {
131
+ if (o.flag.startsWith(name)) {
132
+ candidates.push(o);
133
+ }
134
+ }
135
+ for (const h of HELP_FLAGS) {
136
+ if (h.startsWith(name)) {
137
+ candidates.push('help');
138
+ }
139
+ }
140
+ if (candidates.length === 1) {
141
+ return candidates[0] as OptionSpec | 'help';
142
+ }
143
+ if (candidates.length === 0) {
144
+ // argparse reports unknown options via "unrecognized arguments".
145
+ err(`unrecognized arguments: ${name}`);
146
+ }
147
+ const names = candidates.map((c) => (c === 'help' ? '--help' : c.flag)).join(', ');
148
+ err(`ambiguous option: ${name} could match ${names}`);
149
+ }
150
+
151
+ /**
152
+ * Parse `argv` (the slice after the program name) into a namespace.
153
+ *
154
+ * Mirrors `argparse.ArgumentParser.parse_args`: on `-h`/`--help` it prints
155
+ * a help marker to stdout and exits 0; on any error it prints to stderr and
156
+ * exits 2 (via `process.exitCode`, never `process.exit`, per ADR-200).
157
+ * Returns the parsed namespace on success.
158
+ */
159
+ export function parse_args(argv: string[]): ParsedArgs {
160
+ const ns: ParsedArgs = {
161
+ state_file: DEFAULT_STATE_FILE,
162
+ ticket_file: null,
163
+ prompt_file: null,
164
+ diff_file: null,
165
+ file_file: null,
166
+ persona: null,
167
+ no_hooks: false,
168
+ hooks_config: null,
169
+ };
170
+ const extras: string[] = [];
171
+
172
+ try {
173
+ let i = 0;
174
+ while (i < argv.length) {
175
+ const tok = argv[i] as string;
176
+ if (tok === '-h' || tok === '--help') {
177
+ process.stdout.write(`${usage()}\n`);
178
+ throw new ArgparseExit(0);
179
+ }
180
+ if (tok.startsWith('--')) {
181
+ let name = tok;
182
+ let inlineValue: string | null = null;
183
+ const eq = tok.indexOf('=');
184
+ if (eq !== -1) {
185
+ name = tok.slice(0, eq);
186
+ inlineValue = tok.slice(eq + 1);
187
+ }
188
+ if (name === '-h' || name === '--help') {
189
+ process.stdout.write(`${usage()}\n`);
190
+ throw new ArgparseExit(0);
191
+ }
192
+ const resolved = resolveLong(name);
193
+ if (resolved === 'help') {
194
+ process.stdout.write(`${usage()}\n`);
195
+ throw new ArgparseExit(0);
196
+ }
197
+ if (resolved.takesValue) {
198
+ if (inlineValue !== null) {
199
+ assignValue(ns, resolved, inlineValue);
200
+ } else {
201
+ const next = argv[i + 1];
202
+ // argparse: a value beginning with `-` is still consumed
203
+ // only when it does not look like a known option; the
204
+ // simpler "expected one argument" path covers the
205
+ // missing-trailing-value case the contract exercises.
206
+ if (next === undefined) {
207
+ err(`argument ${resolved.flag}: expected one argument`);
208
+ }
209
+ assignValue(ns, resolved, next);
210
+ i += 1;
211
+ }
212
+ } else {
213
+ // store_true: an explicit `=value` is an error in argparse.
214
+ if (inlineValue !== null) {
215
+ err(`argument ${resolved.flag}: ignored explicit argument '${inlineValue}'`);
216
+ }
217
+ (ns[resolved.dest] as boolean) = true;
218
+ }
219
+ } else if (tok.startsWith('-') && tok !== '-') {
220
+ // Single-dash unknown short option → unrecognized.
221
+ extras.push(tok);
222
+ } else {
223
+ // Positional — this parser declares none, so it is an extra.
224
+ extras.push(tok);
225
+ }
226
+ i += 1;
227
+ }
228
+ if (extras.length > 0) {
229
+ err(`unrecognized arguments: ${extras.join(' ')}`);
230
+ }
231
+ } catch (e) {
232
+ if (e instanceof ArgparseExit) {
233
+ process.exitCode = e.code;
234
+ // Surface as a thrown sentinel so a CLI caller can stop; the test
235
+ // harness inspects process.exitCode (mirrors argparse SystemExit).
236
+ throw e;
237
+ }
238
+ throw e;
239
+ }
240
+ return ns;
241
+ }
242
+
243
+ function assignValue(ns: ParsedArgs, spec: OptionSpec, value: string): void {
244
+ // All value-taking flags here are string/Path; store the raw string.
245
+ (ns[spec.dest] as string) = value;
246
+ }
247
+
248
+ /** Re-exported sentinel so a CLI entry point can recognise the exit path. */
249
+ export { ArgparseExit };
@@ -0,0 +1,225 @@
1
+ /**
2
+ * `DeliveryState` — the only object shared between orchestrator steps.
3
+ *
4
+ * TypeScript twin of `work_engine/delivery_state.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
+ * The shape mirrors `docs/contracts/implement-ticket-flow.md`. No step
9
+ * may invent fields not declared here; extensions require a roadmap
10
+ * amendment plus a flow-contract update.
11
+ *
12
+ * Steps return a `StepResult` with one of three `Outcome` values:
13
+ *
14
+ * - `SUCCESS` — step populated its slice of `DeliveryState` and the
15
+ * dispatcher continues to the next step.
16
+ * - `BLOCKED` — step hit an ambiguity it cannot resolve on its own.
17
+ * `questions` carries pre-formatted numbered options per the
18
+ * `user-interaction` rule. The dispatcher halts.
19
+ * - `PARTIAL` — step populated its slice *and* produced open
20
+ * questions. The dispatcher halts with the same surface as BLOCKED;
21
+ * the calling orchestrator (Phase 3) decides whether to prompt the
22
+ * user to continue or stop.
23
+ *
24
+ * `DeliveryState` is a plain dataclass rather than a typed dict so
25
+ * step handlers can rely on attribute access, defaults, and mutation
26
+ * semantics without resorting to dictionary indirection.
27
+ */
28
+
29
+ /** Arbitrary JSON-ish value, mirroring the Python `Any` fields. */
30
+ export type Any = unknown;
31
+
32
+ /**
33
+ * Terminal outcome of a single step.
34
+ *
35
+ * The Python source subclasses `str` + `Enum`, so each member serialises as
36
+ * its string form. We mirror that with a string-valued const object: the
37
+ * values are exactly the strings the Python enum members carry.
38
+ */
39
+ export const Outcome = {
40
+ SUCCESS: 'success',
41
+ BLOCKED: 'blocked',
42
+ PARTIAL: 'partial',
43
+ } as const;
44
+
45
+ export type Outcome = (typeof Outcome)[keyof typeof Outcome];
46
+
47
+ /**
48
+ * Return value of a single `Step` invocation.
49
+ *
50
+ * `questions` is only populated for `BLOCKED` / `PARTIAL` outcomes. Each
51
+ * entry is a fully-formatted numbered line so the dispatcher can surface
52
+ * them verbatim without reformatting.
53
+ *
54
+ * Field order (outcome, questions, message) mirrors the Python dataclass so
55
+ * an `asdict`-style projection stays key-order identical.
56
+ */
57
+ export class StepResult {
58
+ outcome: Outcome;
59
+ questions: string[];
60
+ message: string;
61
+
62
+ constructor(args: { outcome: Outcome; questions?: string[]; message?: string }) {
63
+ this.outcome = args.outcome;
64
+ // `field(default_factory=list)` → every instance owns its own array.
65
+ this.questions = args.questions ?? [];
66
+ this.message = args.message ?? '';
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Canonical state passed between orchestrator steps.
72
+ *
73
+ * Field order matches the table in `docs/contracts/implement-ticket-flow.md`.
74
+ * Mutable defaults are created per-instance (the Python
75
+ * `field(default_factory=...)` contract) so every instance owns its own
76
+ * containers — a single shared array across runs would be a cross-run
77
+ * contamination hazard for the metrics pipeline.
78
+ */
79
+ export class DeliveryState {
80
+ ticket: Record<string, Any>;
81
+ persona: string;
82
+ memory: Array<Record<string, Any>>;
83
+ plan: Any;
84
+ changes: Array<Record<string, Any>>;
85
+ tests: Any;
86
+ verify: Any;
87
+ outcomes: Record<string, string>;
88
+ questions: string[];
89
+ report: string;
90
+ ui_audit: Record<string, Any> | null;
91
+ app_spec: Record<string, Any> | null;
92
+ ui_design: Record<string, Any> | null;
93
+ ui_scaffold: Record<string, Any> | null;
94
+ ui_review: Record<string, Any> | null;
95
+ ui_polish: Record<string, Any> | null;
96
+ contract: Record<string, Any> | null;
97
+ stitch: Record<string, Any> | null;
98
+ stack: Record<string, Any> | null;
99
+
100
+ constructor(args: {
101
+ ticket: Record<string, Any>;
102
+ persona?: string;
103
+ memory?: Array<Record<string, Any>>;
104
+ plan?: Any;
105
+ changes?: Array<Record<string, Any>>;
106
+ tests?: Any;
107
+ verify?: Any;
108
+ outcomes?: Record<string, string>;
109
+ questions?: string[];
110
+ report?: string;
111
+ ui_audit?: Record<string, Any> | null;
112
+ app_spec?: Record<string, Any> | null;
113
+ ui_design?: Record<string, Any> | null;
114
+ ui_scaffold?: Record<string, Any> | null;
115
+ ui_review?: Record<string, Any> | null;
116
+ ui_polish?: Record<string, Any> | null;
117
+ contract?: Record<string, Any> | null;
118
+ stitch?: Record<string, Any> | null;
119
+ stack?: Record<string, Any> | null;
120
+ }) {
121
+ this.ticket = args.ticket;
122
+ this.persona = args.persona ?? 'senior-engineer';
123
+ this.memory = args.memory ?? [];
124
+ this.plan = args.plan ?? null;
125
+ this.changes = args.changes ?? [];
126
+ this.tests = args.tests ?? null;
127
+ this.verify = args.verify ?? null;
128
+ this.outcomes = args.outcomes ?? {};
129
+ this.questions = args.questions ?? [];
130
+ this.report = args.report ?? '';
131
+ this.ui_audit = args.ui_audit ?? null;
132
+ this.app_spec = args.app_spec ?? null;
133
+ this.ui_design = args.ui_design ?? null;
134
+ this.ui_scaffold = args.ui_scaffold ?? null;
135
+ this.ui_review = args.ui_review ?? null;
136
+ this.ui_polish = args.ui_polish ?? null;
137
+ this.contract = args.contract ?? null;
138
+ this.stitch = args.stitch ?? null;
139
+ this.stack = args.stack ?? null;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Protocol every step handler must satisfy.
145
+ *
146
+ * A step reads and writes `DeliveryState` in place; its return value
147
+ * carries only the terminal `Outcome` and any surfaced questions.
148
+ */
149
+ export type Step = (state: DeliveryState) => StepResult;
150
+
151
+ /**
152
+ * Marker that flags a `questions[0]` entry as agent-addressed, not
153
+ * user-addressed.
154
+ *
155
+ * When a step cannot run deterministically from pure Python (edits,
156
+ * subprocess calls, anything that needs tools the dispatcher doesn't
157
+ * own), it returns `BLOCKED` with this prefix as the first entry of
158
+ * `questions`. The orchestrator reads it and drives the matching
159
+ * skill; the user-facing numbered options follow on subsequent lines.
160
+ *
161
+ * The prefix is public contract: changing it breaks every agent that
162
+ * has learned to recognise it. See
163
+ * `docs/contracts/implement-ticket-flow.md#agent-directives`.
164
+ */
165
+ export const AGENT_DIRECTIVE_PREFIX = '@agent-directive:';
166
+
167
+ /**
168
+ * Python `str(value)` for the scalar value kinds an agent-directive payload
169
+ * carries. The Python source coerces every payload value with `str(...)`, so
170
+ * `True` → `"True"`, `False` → `"False"`, `None` → `"None"`; numbers and
171
+ * strings render as-is.
172
+ */
173
+ function pyStr(value: unknown): string {
174
+ if (value === null || value === undefined) {
175
+ return 'None';
176
+ }
177
+ if (value === true) {
178
+ return 'True';
179
+ }
180
+ if (value === false) {
181
+ return 'False';
182
+ }
183
+ return String(value);
184
+ }
185
+
186
+ /**
187
+ * Format a canonical `@agent-directive:` line.
188
+ *
189
+ * `name` is the directive verb the agent dispatches on (for example
190
+ * `"implement-plan"` or `"run-tests"`). `payload` entries are rendered as
191
+ * `key=value` pairs on the same line, so the whole directive stays a single
192
+ * greppable string. Values are coerced with Python `str()` semantics — richer
193
+ * payloads belong on the `DeliveryState` itself, not in the directive line.
194
+ *
195
+ * Insertion order of `payload` keys is preserved, matching Python's `**kwargs`
196
+ * ordering.
197
+ */
198
+ export function agent_directive(name: string, payload: Record<string, unknown> = {}): string {
199
+ const suffix = Object.entries(payload)
200
+ .map(([key, value]) => `${key}=${pyStr(value)}`)
201
+ .join(' ');
202
+ return suffix
203
+ ? `${AGENT_DIRECTIVE_PREFIX} ${name} ${suffix}`.trim()
204
+ : `${AGENT_DIRECTIVE_PREFIX} ${name}`;
205
+ }
206
+
207
+ /**
208
+ * True when `question` is an agent-addressed directive line.
209
+ *
210
+ * Used by the orchestrator to split `state.questions` into the
211
+ * agent-facing directive (at most one, always at index 0) and the
212
+ * user-facing numbered options (everything else).
213
+ */
214
+ export function is_agent_directive(question: unknown): boolean {
215
+ return typeof question === 'string' && pyLstrip(question).startsWith(AGENT_DIRECTIVE_PREFIX);
216
+ }
217
+
218
+ /**
219
+ * Python `str.lstrip()` with no argument — strips leading whitespace as
220
+ * Python defines it (the `str.isspace` set). JS `String.prototype.trimStart`
221
+ * covers the same Unicode whitespace set for the inputs this module sees.
222
+ */
223
+ function pyLstrip(s: string): string {
224
+ return s.trimStart();
225
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * `analyze` step — deterministic precondition gate.
3
+ *
4
+ * TypeScript twin of `work_engine/directives/backend/analyze.py` (ADR-200
5
+ * py2ts Phase 1 — work_engine directive sets). Public API names stay
6
+ * snake_case to mirror the Python module 1:1 (per ADR-200 — Python style is
7
+ * part of the contract).
8
+ *
9
+ * The step runs no analysis of its own: the real impact analysis is
10
+ * owned by the agent between the `memory` and `plan` steps. The
11
+ * gate's job is to confirm the upstream slices are populated enough
12
+ * for planning to be meaningful.
13
+ *
14
+ * Checks, in order:
15
+ *
16
+ * 1. `refine` outcome must be `success` — the ticket is refined.
17
+ * 2. `memory` outcome must be `success` — priors were queried
18
+ * (an empty result set is still a successful query, per the
19
+ * `memory` step's own contract).
20
+ * 3. The ticket must still carry acceptance criteria — guards against
21
+ * a resuming caller overwriting `state.ticket` between runs.
22
+ *
23
+ * On any missing precondition the step returns `BLOCKED` with the
24
+ * reason spelled out and numbered options per the `user-interaction`
25
+ * rule. Otherwise it returns `SUCCESS` without mutating state.
26
+ */
27
+
28
+ import type { DeliveryState} from '../../delivery_state.js';
29
+ import { Outcome, StepResult } from '../../delivery_state.js';
30
+
31
+ /** Declared ambiguity surfaces. Every BLOCKED return maps to one code. */
32
+ export const AMBIGUITIES: ReadonlyArray<Record<string, string>> = [
33
+ {
34
+ code: 'upstream_refine_failed',
35
+ trigger: '`refine` outcome is not `success`',
36
+ resolution: 're-run `/implement-ticket` from the start',
37
+ },
38
+ {
39
+ code: 'upstream_memory_failed',
40
+ trigger: '`memory` outcome is not `success`',
41
+ resolution: 're-run `/implement-ticket` from the start',
42
+ },
43
+ {
44
+ code: 'lost_ac',
45
+ trigger: 'ticket lost its `acceptance_criteria` between runs',
46
+ resolution: 'restart with the full ticket payload',
47
+ },
48
+ ];
49
+
50
+ /** Return SUCCESS when upstream is coherent, BLOCKED otherwise. */
51
+ export function run(state: DeliveryState): StepResult {
52
+ const missing = _diagnose(state);
53
+ if (missing.length === 0) {
54
+ return new StepResult({ outcome: Outcome.SUCCESS });
55
+ }
56
+
57
+ const ticketId = _pyTruthy((state.ticket ?? {}).id) ? String((state.ticket ?? {}).id) : '(no id)';
58
+ return new StepResult({
59
+ outcome: Outcome.BLOCKED,
60
+ questions: _format_questions(ticketId, missing),
61
+ message: `Ticket ${ticketId} cannot enter the plan step: ` + missing.join('; '),
62
+ });
63
+ }
64
+
65
+ /** List the precondition failures in the order a reader needs them. */
66
+ function _diagnose(state: DeliveryState): string[] {
67
+ const issues: string[] = [];
68
+
69
+ if (state.outcomes.refine !== Outcome.SUCCESS) {
70
+ issues.push('refine step did not complete successfully');
71
+ }
72
+
73
+ if (state.outcomes.memory !== Outcome.SUCCESS) {
74
+ issues.push('memory step did not complete successfully');
75
+ }
76
+
77
+ const ac = (state.ticket ?? {}).acceptance_criteria;
78
+ if (!Array.isArray(ac) || ac.length === 0) {
79
+ issues.push('ticket lost its acceptance criteria');
80
+ }
81
+
82
+ return issues;
83
+ }
84
+
85
+ /**
86
+ * Render the numbered options shown to the user when BLOCKED.
87
+ *
88
+ * Two options: retry from the first failing step, or abort. The
89
+ * headnote names the ticket and every failure so the user can
90
+ * decide without re-reading the earlier output.
91
+ */
92
+ function _format_questions(ticketId: string, missing: string[]): string[] {
93
+ const headnote = `> Ticket ${ticketId} — analyze gate failed: ` + missing.join('; ') + '.';
94
+ return [
95
+ headnote,
96
+ '> 1. Re-run `/implement-ticket` from the start — rebuild upstream state',
97
+ '> 2. Abort — the flow cannot continue',
98
+ ];
99
+ }
100
+
101
+ /** Python truthiness for the scalar/container kinds these slices carry. */
102
+ function _pyTruthy(value: unknown): boolean {
103
+ if (value === null || value === undefined || value === false) {
104
+ return false;
105
+ }
106
+ if (value === true) {
107
+ return true;
108
+ }
109
+ if (typeof value === 'number') {
110
+ return value !== 0;
111
+ }
112
+ if (typeof value === 'string') {
113
+ return value.length !== 0;
114
+ }
115
+ if (Array.isArray(value)) {
116
+ return value.length !== 0;
117
+ }
118
+ if (value instanceof Set || value instanceof Map) {
119
+ return value.size !== 0;
120
+ }
121
+ if (typeof value === 'object') {
122
+ return Object.keys(value as Record<string, unknown>).length !== 0;
123
+ }
124
+ return true;
125
+ }