@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,175 @@
1
+ /**
2
+ * File resolver — wrap a path reference as an {@link Input} envelope.
3
+ *
4
+ * TypeScript twin of `work_engine/resolvers/file.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 existing
9
+ * component/page" surface: a user hands the engine a path (e.g.,
10
+ * `resources/views/dashboard.blade.php` or `src/components/Sidebar.tsx`),
11
+ * the resolver normalises it, and the dispatcher routes the envelope through
12
+ * the UI-track `ui-improve` directive set.
13
+ *
14
+ * Like `work_engine.resolvers.prompt` and `.diff`, this module is
15
+ * intentionally thin: it normalises and rejects garbage payloads, nothing
16
+ * more. Existence checks, mtime caching, and content reads are deferred to
17
+ * the `analyze` step / the audit directive — a resolver doing I/O at the
18
+ * command-shell boundary would couple the envelope build to filesystem state
19
+ * and break replay-against-state-files.
20
+ *
21
+ * The envelope mirrors the prompt and diff resolvers so a single refiner code
22
+ * path can read all three — `{path, reconstructed_ac, assumptions}`. The
23
+ * only material difference is the path-shape check that rejects values that
24
+ * are obviously not paths (absolute URLs, empty strings, control chars).
25
+ */
26
+ import * as path from 'node:path';
27
+
28
+ import { Input } from '../state.js';
29
+
30
+ export const KIND = 'file';
31
+ /** Wire value carried in {@link Input.kind}. */
32
+
33
+ const _MIN_PATH_LEN = 1;
34
+ /**
35
+ * Minimum non-whitespace character count for a resolvable path.
36
+ *
37
+ * A 1-char path is rare but legal (`a`); the bar exists only to reject
38
+ * literal empty / whitespace-only payloads which carry no signal at all.
39
+ */
40
+
41
+ const _FORBIDDEN_PREFIXES: readonly string[] = [
42
+ 'http://',
43
+ 'https://',
44
+ 'ftp://',
45
+ 'file://',
46
+ ];
47
+ /**
48
+ * Prefixes that signal the caller passed a URL, not a filesystem path.
49
+ *
50
+ * The diff resolver handles patch URLs at a future R3 layer; the file
51
+ * resolver only accepts on-disk references. Rejecting URLs explicitly
52
+ * keeps misuse loud instead of letting the audit step discover it later.
53
+ * The check is case-insensitive.
54
+ */
55
+
56
+ /** Raised when a payload cannot be resolved into a file envelope. */
57
+ export class FileResolverError extends Error {
58
+ constructor(message: string) {
59
+ super(message);
60
+ this.name = 'FileResolverError';
61
+ Object.setPrototypeOf(this, FileResolverError.prototype);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Return an {@link Input} carrying the path + empty refinement slots.
67
+ *
68
+ * @param path_ The user-supplied path reference. The resolver normalises only
69
+ * by stripping leading/trailing whitespace; case, separators, and the
70
+ * relative-vs-absolute distinction are all preserved verbatim so the
71
+ * downstream audit step reads exactly what the user wrote.
72
+ * @returns Envelope of shape
73
+ * `{"kind": "file", "data": {"path": <path>, "reconstructed_ac": [],
74
+ * "assumptions": []}}`. The two empty lists are placeholders the
75
+ * `refine-prompt` skill writes into on the rebound from `refine`; they are
76
+ * kept to preserve a single-shape envelope across all prompt-like resolvers.
77
+ * @throws {@link FileResolverError} If `path` is not a string, is empty /
78
+ * whitespace-only, contains a NUL byte (filesystem-illegal everywhere), or
79
+ * is a URL (use the diff resolver for remote-PR / patch URLs in a future R3
80
+ * phase).
81
+ */
82
+ export function build_envelope(path_: unknown): Input {
83
+ if (typeof path_ !== 'string') {
84
+ throw new FileResolverError(
85
+ `path must be a string; got ${pyTypeName(path_)}`,
86
+ );
87
+ }
88
+ const stripped = pyStrip(path_);
89
+ if (stripped.length < _MIN_PATH_LEN) {
90
+ throw new FileResolverError(
91
+ 'path is empty or whitespace-only — nothing to resolve',
92
+ );
93
+ }
94
+ if (stripped.includes('\x00')) {
95
+ throw new FileResolverError(
96
+ 'path contains a NUL byte; filesystem references must be ' +
97
+ 'NUL-free',
98
+ );
99
+ }
100
+ const lowered = stripped.toLowerCase();
101
+ if (_FORBIDDEN_PREFIXES.some((prefix) => lowered.startsWith(prefix))) {
102
+ throw new FileResolverError(
103
+ `path looks like a URL (${pyStrRepr(pySlice(stripped, 32))}); the file resolver ` +
104
+ 'only accepts on-disk references — use the diff resolver for ' +
105
+ 'PR or patch URLs',
106
+ );
107
+ }
108
+ // Normalise separators *only* on Windows-style backslashes so
109
+ // `resources\\views\\foo.blade.php` round-trips as POSIX. Native
110
+ // POSIX paths are returned untouched so the audit step's identity
111
+ // comparison against directory listings stays trivial.
112
+ const normalised =
113
+ path.sep === '/' ? stripped.replace(/\\/g, '/') : stripped;
114
+ return new Input(KIND, {
115
+ path: normalised,
116
+ reconstructed_ac: [],
117
+ assumptions: [],
118
+ });
119
+ }
120
+
121
+ // ── Python-parity helpers ───────────────────────────────────────────────
122
+
123
+ /**
124
+ * Mirror Python `str.strip()` — see `resolvers/prompt.ts` for the parity note.
125
+ */
126
+ function pyStrip(s: string): string {
127
+ return s.trim();
128
+ }
129
+
130
+ /**
131
+ * Mirror Python `s[:n]` over code points. The error path renders `stripped[:32]`
132
+ * via `!r`; Python slices by code point, not UTF-16 unit, so use the spread
133
+ * iterator to slice on code-point boundaries before re-joining.
134
+ */
135
+ function pySlice(s: string, n: number): string {
136
+ return [...s].slice(0, n).join('');
137
+ }
138
+
139
+ /**
140
+ * Mirror Python `repr(str)` — prefers single quotes, switches to double quotes
141
+ * only when the string contains a single quote but no double quote, and escapes
142
+ * backslash plus the active quote and the standard control characters. Matches
143
+ * the `state.py` twin's `pyStrRepr` so the `{...!r}` error tail is byte-equal.
144
+ */
145
+ function pyStrRepr(s: string): string {
146
+ const hasSingle = s.includes("'");
147
+ const hasDouble = s.includes('"');
148
+ const quote = hasSingle && !hasDouble ? '"' : "'";
149
+ let out = quote;
150
+ for (const ch of s) {
151
+ const code = ch.codePointAt(0) as number;
152
+ if (ch === '\\') out += '\\\\';
153
+ else if (ch === quote) out += '\\' + quote;
154
+ else if (ch === '\n') out += '\\n';
155
+ else if (ch === '\r') out += '\\r';
156
+ else if (ch === '\t') out += '\\t';
157
+ else if (code < 0x20 || code === 0x7f) {
158
+ out += `\\x${code.toString(16).padStart(2, '0')}`;
159
+ } else {
160
+ out += ch;
161
+ }
162
+ }
163
+ return out + quote;
164
+ }
165
+
166
+ /** Python `type(x).__name__` for the non-string `isinstance` guard branch. */
167
+ function pyTypeName(v: unknown): string {
168
+ if (v === null) return 'NoneType';
169
+ if (v === undefined) return 'NoneType';
170
+ if (typeof v === 'boolean') return 'bool';
171
+ if (typeof v === 'string') return 'str';
172
+ if (typeof v === 'number') return Number.isInteger(v) ? 'int' : 'float';
173
+ if (Array.isArray(v)) return 'list';
174
+ return 'dict';
175
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Prompt resolver — wrap a raw user prompt as an {@link Input} envelope.
3
+ *
4
+ * TypeScript twin of `work_engine/resolvers/prompt.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 intentionally minimal. It accepts a raw string, validates
9
+ * that it contains non-whitespace content, and returns
10
+ * `Input(kind="prompt", data={"raw": <text>, "reconstructed_ac": [],
11
+ * "assumptions": []})`. The empty AC + assumptions lists are placeholders
12
+ * that the `refine-prompt` skill (R2 Phase 3) fills in once the engine
13
+ * runs the deterministic `refine` gate against the raw text.
14
+ *
15
+ * Why split the resolver from the refiner:
16
+ *
17
+ * - The resolver runs at command boundaries (the `/work` entrypoint
18
+ * builds an envelope, then hands off to `work_engine`). It must stay
19
+ * side-effect-free and dependency-light so the command shell can call
20
+ * it without touching the LLM-facing skill harness.
21
+ * - The refiner runs inside the dispatcher loop and is allowed to halt
22
+ * (medium-confidence assumptions report, low-confidence one-question
23
+ * block) per `docs/contracts/implement-ticket-flow.md`. That
24
+ * control-flow surface does not belong in a resolver.
25
+ *
26
+ * Future R3 resolvers (`diff`, `file`) follow the same pattern: thin
27
+ * normalisation, no interpretation, one envelope shape per kind.
28
+ */
29
+ import { Input } from '../state.js';
30
+
31
+ export const KIND = 'prompt';
32
+ /** Wire value carried in {@link Input.kind}. */
33
+
34
+ const _MIN_PROMPT_LEN = 1;
35
+ /**
36
+ * Minimum non-whitespace character count for a resolvable prompt.
37
+ *
38
+ * Set to 1 by design — the resolver is not a quality gate. It only
39
+ * rejects literally empty / whitespace-only payloads (which cannot be
40
+ * distinguished from missing input). Quality judgment (is the prompt
41
+ * clear? is it tractable?) is the `refine-prompt` skill's job, surfaced
42
+ * through the confidence band, not the resolver's.
43
+ */
44
+
45
+ /** Raised when a payload cannot be resolved into a prompt envelope. */
46
+ export class PromptResolverError extends Error {
47
+ constructor(message: string) {
48
+ super(message);
49
+ this.name = 'PromptResolverError';
50
+ Object.setPrototypeOf(this, PromptResolverError.prototype);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Return an {@link Input} carrying the raw prompt + empty refinement slots.
56
+ *
57
+ * @param raw The user-supplied prompt text. Leading/trailing whitespace is
58
+ * preserved verbatim — the refiner reads the original casing and spacing
59
+ * when scoring goal clarity, so collapsing whitespace here would lose
60
+ * signal.
61
+ * @returns Envelope of shape
62
+ * `{"kind": "prompt", "data": {"raw": <raw>, "reconstructed_ac": [],
63
+ * "assumptions": []}}`. The two empty lists are placeholders the
64
+ * `refine-prompt` skill writes into on the rebound from the `refine` step.
65
+ * @throws {@link PromptResolverError} If `raw` is not a string, or contains
66
+ * no non-whitespace characters (the only case where the envelope would
67
+ * carry no actionable signal at all).
68
+ */
69
+ export function build_envelope(raw: unknown): Input {
70
+ if (typeof raw !== 'string') {
71
+ throw new PromptResolverError(
72
+ `prompt must be a string; got ${pyTypeName(raw)}`,
73
+ );
74
+ }
75
+ if (pyStrip(raw).length < _MIN_PROMPT_LEN) {
76
+ throw new PromptResolverError(
77
+ 'prompt is empty or whitespace-only — nothing to resolve',
78
+ );
79
+ }
80
+ return new Input(KIND, {
81
+ raw: raw,
82
+ reconstructed_ac: [],
83
+ assumptions: [],
84
+ });
85
+ }
86
+
87
+ // ── Python-parity helpers ───────────────────────────────────────────────
88
+
89
+ /**
90
+ * Mirror Python `str.strip()` — strips the Unicode whitespace set CPython
91
+ * uses (the characters for which `str.isspace()` is true). `String.trim()`
92
+ * strips a near-identical set; the only practical divergence for the empty-
93
+ * payload guard is that `trim()` does not strip a handful of exotic code
94
+ * points (e.g. zero-width). For a length-vs-1 gate the standard `trim` is
95
+ * faithful for every realistic whitespace-only prompt, and any leftover
96
+ * exotic char would correctly count as "non-whitespace content" under both
97
+ * runtimes' intent. Kept as a named helper to mark the parity point.
98
+ */
99
+ function pyStrip(s: string): string {
100
+ return s.trim();
101
+ }
102
+
103
+ /**
104
+ * Python `type(x).__name__` for the values this resolver's TypeError-equivalent
105
+ * branch reports: the `isinstance(raw, str)` guard fires on any non-string.
106
+ */
107
+ function pyTypeName(v: unknown): string {
108
+ if (v === null) return 'NoneType';
109
+ if (v === undefined) return 'NoneType';
110
+ if (typeof v === 'boolean') return 'bool';
111
+ if (typeof v === 'string') return 'str';
112
+ if (typeof v === 'number') return Number.isInteger(v) ? 'int' : 'float';
113
+ if (Array.isArray(v)) return 'list';
114
+ return 'dict';
115
+ }