let-them-talk 5.3.0 → 5.4.0

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 (166) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +158 -592
  3. package/SECURITY.md +3 -3
  4. package/USAGE.md +151 -0
  5. package/agent-contracts.js +447 -0
  6. package/api-agents.js +760 -0
  7. package/autonomy/decision-v2.js +380 -0
  8. package/autonomy/watchdog-policy.js +572 -0
  9. package/cli.js +454 -298
  10. package/conversation-templates/autonomous-feature.json +83 -22
  11. package/conversation-templates/code-review.json +69 -21
  12. package/conversation-templates/debug-squad.json +69 -21
  13. package/conversation-templates/feature-build.json +69 -21
  14. package/conversation-templates/research-write.json +69 -21
  15. package/dashboard.html +3148 -174
  16. package/dashboard.js +823 -786
  17. package/data-dir.js +58 -0
  18. package/docs/architecture/branch-semantics.md +157 -0
  19. package/docs/architecture/canonical-event-schema.md +88 -0
  20. package/docs/architecture/markdown-workspace.md +183 -0
  21. package/docs/architecture/runtime-contract.md +459 -0
  22. package/docs/architecture/runtime-migration-hardening.md +64 -0
  23. package/events/hooks.js +154 -0
  24. package/events/log.js +457 -0
  25. package/events/replay.js +33 -0
  26. package/events/schema.js +432 -0
  27. package/managed-team-integration.js +261 -0
  28. package/office/agents.js +704 -597
  29. package/office/animation.js +1 -1
  30. package/office/assets/arcade-cabinet.js +141 -0
  31. package/office/assets/archway.js +77 -0
  32. package/office/assets/bar-counter.js +91 -0
  33. package/office/assets/bar-stool.js +71 -0
  34. package/office/assets/beanbag.js +64 -0
  35. package/office/assets/bench.js +99 -0
  36. package/office/assets/bollard.js +87 -0
  37. package/office/assets/cactus.js +100 -0
  38. package/office/assets/carpet-tile.js +46 -0
  39. package/office/assets/chair.js +123 -0
  40. package/office/assets/chandelier.js +107 -0
  41. package/office/assets/coffee-machine.js +95 -0
  42. package/office/assets/coffee-table.js +81 -0
  43. package/office/assets/column.js +95 -0
  44. package/office/assets/desk-lamp.js +102 -0
  45. package/office/assets/desk.js +76 -0
  46. package/office/assets/dining-table.js +105 -0
  47. package/office/assets/door.js +70 -0
  48. package/office/assets/dual-monitor.js +72 -0
  49. package/office/assets/fence.js +76 -0
  50. package/office/assets/filing-cabinet.js +111 -0
  51. package/office/assets/floor-lamp.js +69 -0
  52. package/office/assets/floor-tile.js +54 -0
  53. package/office/assets/flower-pot.js +76 -0
  54. package/office/assets/foosball.js +95 -0
  55. package/office/assets/fridge.js +99 -0
  56. package/office/assets/gaming-chair.js +154 -0
  57. package/office/assets/gaming-desk.js +105 -0
  58. package/office/assets/glass-door.js +72 -0
  59. package/office/assets/glass-wall.js +64 -0
  60. package/office/assets/half-wall.js +49 -0
  61. package/office/assets/hanging-plant.js +112 -0
  62. package/office/assets/index.js +151 -0
  63. package/office/assets/indoor-tree.js +90 -0
  64. package/office/assets/l-sofa.js +153 -0
  65. package/office/assets/marble-floor.js +64 -0
  66. package/office/assets/materials.js +40 -0
  67. package/office/assets/meeting-table.js +88 -0
  68. package/office/assets/microwave.js +94 -0
  69. package/office/assets/monitor.js +67 -0
  70. package/office/assets/neon-strip.js +73 -0
  71. package/office/assets/painting.js +84 -0
  72. package/office/assets/palm-tree.js +108 -0
  73. package/office/assets/pc-tower.js +91 -0
  74. package/office/assets/pendant-light.js +67 -0
  75. package/office/assets/ping-pong.js +114 -0
  76. package/office/assets/plant.js +72 -0
  77. package/office/assets/planter-box.js +95 -0
  78. package/office/assets/pool-table.js +94 -0
  79. package/office/assets/printer.js +113 -0
  80. package/office/assets/reception-desk.js +133 -0
  81. package/office/assets/rug.js +78 -0
  82. package/office/assets/sculpture.js +85 -0
  83. package/office/assets/server-rack.js +98 -0
  84. package/office/assets/sink.js +109 -0
  85. package/office/assets/sofa.js +106 -0
  86. package/office/assets/speaker.js +83 -0
  87. package/office/assets/spotlight.js +83 -0
  88. package/office/assets/street-lamp.js +97 -0
  89. package/office/assets/trash-can.js +83 -0
  90. package/office/assets/treadmill.js +126 -0
  91. package/office/assets/trophy.js +89 -0
  92. package/office/assets/tv-screen.js +79 -0
  93. package/office/assets/vase.js +84 -0
  94. package/office/assets/wall-clock.js +84 -0
  95. package/office/assets/wall.js +53 -0
  96. package/office/assets/water-cooler.js +146 -0
  97. package/office/assets/whiteboard.js +115 -0
  98. package/office/assets.js +3 -431
  99. package/office/builder.js +791 -355
  100. package/office/campus-env.js +1012 -1119
  101. package/office/environment.js +2 -0
  102. package/office/gallery.js +997 -0
  103. package/office/index.js +165 -61
  104. package/office/navigation.js +173 -152
  105. package/office/player.js +178 -68
  106. package/office/robot-character.js +272 -0
  107. package/office/spectator-camera.js +33 -10
  108. package/office/state.js +2 -0
  109. package/office/world-save.js +35 -4
  110. package/package.json +57 -3
  111. package/providers/comfyui.js +383 -0
  112. package/providers/dalle.js +79 -0
  113. package/providers/gemini.js +181 -0
  114. package/providers/ollama.js +184 -0
  115. package/providers/replicate.js +115 -0
  116. package/providers/zai.js +183 -0
  117. package/runtime-descriptor.js +270 -0
  118. package/scripts/check-agent-contract-advisory.js +132 -0
  119. package/scripts/check-api-agent-parity.js +277 -0
  120. package/scripts/check-autonomy-v2-decision.js +207 -0
  121. package/scripts/check-autonomy-v2-execution.js +588 -0
  122. package/scripts/check-autonomy-v2-watchdog.js +224 -0
  123. package/scripts/check-branch-fork-snapshot.js +337 -0
  124. package/scripts/check-branch-isolation.js +787 -0
  125. package/scripts/check-branch-semantics.js +139 -0
  126. package/scripts/check-dashboard-control-plane.js +1304 -0
  127. package/scripts/check-docs-onboarding.js +490 -0
  128. package/scripts/check-event-schema.js +276 -0
  129. package/scripts/check-evidence-completion.js +239 -0
  130. package/scripts/check-invariants.js +992 -0
  131. package/scripts/check-lifecycle-hooks.js +525 -0
  132. package/scripts/check-managed-team-integration.js +166 -0
  133. package/scripts/check-markdown-workspace-export.js +548 -0
  134. package/scripts/check-markdown-workspace-safety.js +347 -0
  135. package/scripts/check-markdown-workspace.js +136 -0
  136. package/scripts/check-message-replay.js +429 -0
  137. package/scripts/check-migration-hardening.js +300 -0
  138. package/scripts/check-performance-indexing.js +272 -0
  139. package/scripts/check-provider-capabilities.js +316 -0
  140. package/scripts/check-runtime-contract.js +109 -0
  141. package/scripts/check-session-aware-context.js +172 -0
  142. package/scripts/check-session-lifecycle.js +210 -0
  143. package/scripts/export-markdown-workspace.js +84 -0
  144. package/scripts/fixtures/message-replay/clean.jsonl +2 -0
  145. package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
  146. package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
  147. package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
  148. package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
  149. package/scripts/migrate-legacy-to-canonical.js +201 -0
  150. package/scripts/run-verification-suite.js +242 -0
  151. package/scripts/sync-packaged-docs.js +69 -0
  152. package/server.js +9546 -7216
  153. package/state/agents.js +161 -0
  154. package/state/canonical.js +3068 -0
  155. package/state/dashboard-queries.js +441 -0
  156. package/state/evidence.js +56 -0
  157. package/state/io.js +69 -0
  158. package/state/markdown-workspace.js +951 -0
  159. package/state/messages.js +669 -0
  160. package/state/sessions.js +683 -0
  161. package/state/tasks-workflows.js +92 -0
  162. package/templates/debate.json +2 -2
  163. package/templates/managed.json +4 -4
  164. package/templates/pair.json +2 -2
  165. package/templates/review.json +2 -2
  166. package/templates/team.json +3 -3
package/SECURITY.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | ------- | ------------------ |
7
- | 3.x.x | Yes |
8
- | 2.x.x | No |
9
- | < 2.0 | No |
7
+ | 5.x.x | Yes |
8
+ | 4.x.x | No |
9
+ | < 4.0 | No |
10
10
 
11
11
  ## Reporting a Vulnerability
12
12
 
package/USAGE.md ADDED
@@ -0,0 +1,151 @@
1
+ <!-- Generated from ../USAGE.md by scripts/sync-packaged-docs.js for published package consumers. -->
2
+
3
+ # Let Them Talk Usage Guide v5.4.0
4
+
5
+ This guide is the short operator view of the current runtime. For normative architecture details, use the docs under `docs/architecture/`.
6
+
7
+ ## Install and launch
8
+
9
+ ```bash
10
+ npx let-them-talk init
11
+ npx let-them-talk init --claude
12
+ npx let-them-talk init --gemini
13
+ npx let-them-talk init --codex
14
+ npx let-them-talk init --all
15
+ npx let-them-talk init --ollama
16
+ npx let-them-talk init --template <name>
17
+ ```
18
+
19
+ After init, use the local launcher from the project:
20
+
21
+ ```bash
22
+ node .agent-bridge/launch.js
23
+ node .agent-bridge/launch.js --lan
24
+ node .agent-bridge/launch.js status
25
+ node .agent-bridge/launch.js msg <agent> <text>
26
+ node .agent-bridge/launch.js reset
27
+ ```
28
+
29
+ Packaged helpers still available through `npx`:
30
+
31
+ ```bash
32
+ npx let-them-talk dashboard
33
+ npx let-them-talk status
34
+ npx let-them-talk templates
35
+ npx let-them-talk uninstall
36
+ npx let-them-talk help
37
+ ```
38
+
39
+ ## Join, listen, and resume work
40
+
41
+ Recommended entry sequence for an agent:
42
+
43
+ 1. Register an agent name.
44
+ 2. Call `get_briefing()` if the project already has history, tasks, or decisions.
45
+ 3. Call `get_guide()` if you need the current collaboration rules for the active mode.
46
+
47
+ Current loop guidance:
48
+
49
+ - Direct mode: use `listen()`.
50
+ - Group or managed mode: use `listen_group()`.
51
+ - Codex CLI compatibility path: use `listen_codex()` when the runtime instructs it.
52
+ - Proactive autonomy loop: use `get_work()` and finish work with `verify_and_advance()`.
53
+
54
+ ## Branch, session, and evidence model
55
+
56
+ - Canonical runtime truth is event-backed under `.agent-bridge/runtime/`.
57
+ - Legacy JSON and JSONL files are compatibility projections during migration. They are not the authority model.
58
+ - The runtime contract treats branches as full-context namespaces. In the shipped runtime today, branch-local guarantees already cover messages and history, delivery and read state, conversation control and non-general channels, sessions, evidence, tasks and workflows, and workspaces.
59
+ - Branch-local guarantees now also cover the governance surfaces that used to remain compatibility-shared during migration: decisions, KB, reviews, dependencies, votes, rules, and progress.
60
+ - A branch switch changes the whole migrated branch-local collaboration view, not only message history.
61
+ - Sessions are scoped to one agent on one branch. Rejoining the same branch resumes that branch-scoped context, switching branches suspends one branch session and creates or resumes another, and forks carry historical session and evidence context without cloning live sessions.
62
+ - Completion is only authoritative when structured evidence is recorded.
63
+ - The current evidence payload centers on `summary`, `verification`, `files_changed`, `confidence`, `recorded_at`, and `recorded_by_session`.
64
+
65
+ ## Runtime descriptors and provider capabilities
66
+
67
+ API-backed agents now persist an explicit runtime descriptor with:
68
+
69
+ - `runtime_type`
70
+ - `provider_id`
71
+ - `model_id`
72
+ - `capabilities`
73
+
74
+ Supported capability tokens:
75
+
76
+ - `chat`
77
+ - `vision`
78
+ - `image_generation`
79
+ - `video_generation`
80
+ - `texture_generation`
81
+
82
+ Legacy `provider`, `provider_color`, and `bot_capability` fields remain compatibility projections over the explicit descriptor.
83
+
84
+ ## Templates shipped today
85
+
86
+ Agent templates:
87
+
88
+ - `pair`
89
+ - `team`
90
+ - `review`
91
+ - `debate`
92
+ - `managed`
93
+
94
+ Conversation templates:
95
+
96
+ - `autonomous-feature`
97
+ - `code-review`
98
+ - `debug-squad`
99
+ - `feature-build`
100
+ - `research-write`
101
+
102
+ ## Markdown workspace export and safety
103
+
104
+ ```bash
105
+ npm run export:markdown-workspace
106
+ ```
107
+
108
+ Current markdown workspace rules:
109
+
110
+ - default export root is `<project>/.agent-bridge-markdown/`
111
+ - exported files declare `authoritative: false`
112
+ - export is one-way only
113
+ - markdown edits do not write back into runtime state
114
+ - there is no watcher loop or implicit markdown import path
115
+ - compatibility-shared and main-only surfaces are exported only for `main` or omitted, never fabricated into non-main branch folders
116
+
117
+ ## Verification
118
+
119
+ Package directory:
120
+
121
+ ```bash
122
+ npm test
123
+ ```
124
+
125
+ Grouped package commands:
126
+
127
+ ```bash
128
+ npm run verify
129
+ npm run verify:contracts
130
+ npm run verify:replay
131
+ npm run verify:invariants
132
+ npm run verify:smoke
133
+ ```
134
+
135
+ Grouped coverage today:
136
+
137
+ - `verify:contracts` checks the runtime contract, canonical event schema, branch semantics, and markdown workspace contract.
138
+ - `verify:replay` checks healthy and clean replay plus expected-failure negative replay scenarios.
139
+ - `verify:invariants` checks authority routing, dashboard control plane behavior, performance and indexing, provider capabilities, API-agent parity, dashboard semantic-gap coverage, migration hardening, branch isolation, session lifecycle, evidence-backed completion, session-aware context, autonomy v2, advisory contracts, managed-team integration, lifecycle hooks, and markdown workspace export and safety.
140
+ - `verify:smoke` runs a representative subset, including the dashboard semantic-gap check.
141
+
142
+ Coverage is still partial. The suite does not claim a full provider or runtime matrix, and it does not include browser automation.
143
+
144
+ ## Source of truth references
145
+
146
+ - `README.md`
147
+ - `docs/architecture/runtime-contract.md`
148
+ - `docs/architecture/branch-semantics.md`
149
+ - `docs/architecture/canonical-event-schema.md`
150
+ - `docs/architecture/markdown-workspace.md`
151
+ - `docs/architecture/runtime-migration-hardening.md`
@@ -0,0 +1,447 @@
1
+ const VALID_AGENT_ARCHETYPES = Object.freeze([
2
+ 'generalist',
3
+ 'coordinator',
4
+ 'implementer',
5
+ 'reviewer',
6
+ 'advisor',
7
+ 'monitor',
8
+ ]);
9
+
10
+ const VALID_CONTRACT_MODES = Object.freeze(['advisory', 'strict']);
11
+
12
+ const ROLE_TOKEN_ALIASES = Object.freeze({
13
+ lead: 'lead',
14
+ manager: 'manager',
15
+ coordinator: 'coordinator',
16
+ architect: 'architect',
17
+ backend: 'backend',
18
+ frontend: 'frontend',
19
+ implementer: 'implementer',
20
+ developer: 'implementer',
21
+ coder: 'implementer',
22
+ quality: 'quality',
23
+ 'quality lead': 'quality',
24
+ reviewer: 'reviewer',
25
+ advisor: 'advisor',
26
+ monitor: 'monitor',
27
+ });
28
+
29
+ function freezeArchetype(definition) {
30
+ return Object.freeze({
31
+ label: definition.label,
32
+ summary: definition.summary,
33
+ compatible_roles: Object.freeze([...(definition.compatible_roles || [])]),
34
+ default_skills: Object.freeze([...(definition.default_skills || [])]),
35
+ keywords: Object.freeze([...(definition.keywords || [])]),
36
+ preferred_work_types: Object.freeze([...(definition.preferred_work_types || [])]),
37
+ discouraged_work_types: Object.freeze([...(definition.discouraged_work_types || [])]),
38
+ });
39
+ }
40
+
41
+ const AGENT_ARCHETYPE_REGISTRY = Object.freeze({
42
+ generalist: freezeArchetype({
43
+ label: 'Generalist',
44
+ summary: 'Flexible contributor who can work across planning, implementation, review, and coordination as needed.',
45
+ compatible_roles: ['lead', 'manager', 'coordinator', 'architect', 'backend', 'frontend', 'implementer', 'quality', 'reviewer', 'advisor', 'monitor'],
46
+ default_skills: [],
47
+ keywords: ['general', 'support'],
48
+ preferred_work_types: ['workflow_step', 'claimed_task', 'task', 'review', 'help_teammate', 'unblock', 'prep_work', 'messages', 'team_coordination', 'managed_manager'],
49
+ discouraged_work_types: [],
50
+ }),
51
+ coordinator: freezeArchetype({
52
+ label: 'Coordinator',
53
+ summary: 'Best at planning, triage, delegation, and keeping the team moving.',
54
+ compatible_roles: ['lead', 'manager', 'coordinator'],
55
+ default_skills: ['planning', 'coordination', 'delegation'],
56
+ keywords: ['plan', 'planning', 'coordinate', 'coordination', 'delegate', 'workflow', 'triage'],
57
+ preferred_work_types: ['messages', 'help_teammate', 'unblock', 'prep_work', 'review', 'team_coordination', 'managed_manager'],
58
+ discouraged_work_types: [],
59
+ }),
60
+ implementer: freezeArchetype({
61
+ label: 'Implementer',
62
+ summary: 'Best at building, fixing, and shipping concrete code changes.',
63
+ compatible_roles: ['architect', 'backend', 'frontend', 'implementer'],
64
+ default_skills: ['implementation', 'backend', 'frontend', 'testing'],
65
+ keywords: ['implement', 'implementation', 'build', 'fix', 'code', 'refactor', 'backend', 'frontend', 'api', 'ui', 'bug', 'test'],
66
+ preferred_work_types: ['workflow_step', 'claimed_task', 'task', 'prep_work', 'help_teammate'],
67
+ discouraged_work_types: [],
68
+ }),
69
+ reviewer: freezeArchetype({
70
+ label: 'Reviewer',
71
+ summary: 'Best at review, verification, test emphasis, and defect finding.',
72
+ compatible_roles: ['quality', 'reviewer'],
73
+ default_skills: ['review', 'testing', 'verification'],
74
+ keywords: ['review', 'verify', 'verification', 'test', 'testing', 'qa', 'quality', 'bug', 'security'],
75
+ preferred_work_types: ['review', 'unblock', 'help_teammate'],
76
+ discouraged_work_types: [],
77
+ }),
78
+ advisor: freezeArchetype({
79
+ label: 'Advisor',
80
+ summary: 'Best at analysis, architecture, strategy, and guidance rather than task claiming.',
81
+ compatible_roles: ['advisor'],
82
+ default_skills: ['analysis', 'architecture', 'strategy'],
83
+ keywords: ['analysis', 'architecture', 'strategy', 'design', 'research', 'plan'],
84
+ preferred_work_types: ['messages', 'help_teammate', 'prep_work', 'unblock', 'advisor_context'],
85
+ discouraged_work_types: ['workflow_step', 'claimed_task', 'task', 'team_coordination', 'managed_manager'],
86
+ }),
87
+ monitor: freezeArchetype({
88
+ label: 'Monitor',
89
+ summary: 'Best at health checks, triage, queue balancing, and unblocking the team.',
90
+ compatible_roles: ['monitor'],
91
+ default_skills: ['monitoring', 'triage', 'coordination'],
92
+ keywords: ['monitor', 'health', 'triage', 'stuck', 'queue', 'rebalance', 'intervention'],
93
+ preferred_work_types: ['messages', 'help_teammate', 'unblock', 'idle', 'monitor_report'],
94
+ discouraged_work_types: ['workflow_step', 'claimed_task', 'task', 'team_coordination', 'managed_manager'],
95
+ }),
96
+ });
97
+
98
+ const VALID_ARCHETYPE_SET = new Set(VALID_AGENT_ARCHETYPES);
99
+ const VALID_CONTRACT_MODE_SET = new Set(VALID_CONTRACT_MODES);
100
+ const KNOWN_ROLE_TOKENS = new Set(Object.values(ROLE_TOKEN_ALIASES));
101
+
102
+ function normalizeText(value) {
103
+ if (typeof value !== 'string') return null;
104
+ const trimmed = value.trim();
105
+ return trimmed ? trimmed : null;
106
+ }
107
+
108
+ function normalizeLowerToken(value) {
109
+ const text = normalizeText(value);
110
+ if (!text) return null;
111
+ return text.toLowerCase().replace(/[_-]+/g, ' ').replace(/\s+/g, ' ').trim();
112
+ }
113
+
114
+ function normalizeRoleToken(value) {
115
+ const normalized = normalizeLowerToken(value);
116
+ if (!normalized) return null;
117
+ if (ROLE_TOKEN_ALIASES[normalized]) return ROLE_TOKEN_ALIASES[normalized];
118
+ if (normalized.indexOf('implementer') === 0) return 'implementer';
119
+ return normalized;
120
+ }
121
+
122
+ function normalizeArchetype(value) {
123
+ const normalized = normalizeLowerToken(value);
124
+ if (!normalized) return null;
125
+ return VALID_ARCHETYPE_SET.has(normalized) ? normalized : null;
126
+ }
127
+
128
+ function normalizeContractMode(value) {
129
+ const normalized = normalizeLowerToken(value);
130
+ if (!normalized) return null;
131
+ return VALID_CONTRACT_MODE_SET.has(normalized) ? normalized : null;
132
+ }
133
+
134
+ function normalizeSkillToken(value) {
135
+ return normalizeLowerToken(value);
136
+ }
137
+
138
+ function normalizeWorkType(value) {
139
+ const text = normalizeText(value);
140
+ if (!text) return null;
141
+ return text.toLowerCase().replace(/[\s-]+/g, '_');
142
+ }
143
+
144
+ function normalizeSkills(value) {
145
+ const entries = Array.isArray(value)
146
+ ? value
147
+ : (value == null ? [] : [value]);
148
+ const normalized = [];
149
+ const seen = new Set();
150
+
151
+ for (const entry of entries) {
152
+ const token = normalizeSkillToken(entry);
153
+ if (!token || seen.has(token)) continue;
154
+ seen.add(token);
155
+ normalized.push(token);
156
+ }
157
+
158
+ return normalized;
159
+ }
160
+
161
+ function mergeSkills() {
162
+ const merged = [];
163
+ const seen = new Set();
164
+
165
+ for (const list of arguments) {
166
+ for (const entry of Array.isArray(list) ? list : []) {
167
+ const token = normalizeSkillToken(entry);
168
+ if (!token || seen.has(token)) continue;
169
+ seen.add(token);
170
+ merged.push(token);
171
+ }
172
+ }
173
+
174
+ return merged;
175
+ }
176
+
177
+ function inferArchetypeFromRole(roleToken) {
178
+ if (!roleToken) return null;
179
+ const orderedArchetypes = ['coordinator', 'implementer', 'reviewer', 'advisor', 'monitor', 'generalist'];
180
+ for (const archetype of orderedArchetypes) {
181
+ const definition = AGENT_ARCHETYPE_REGISTRY[archetype];
182
+ if (definition.compatible_roles.includes(roleToken)) return archetype;
183
+ }
184
+ return null;
185
+ }
186
+
187
+ function createDefaultContractMetadata() {
188
+ return {
189
+ skills: [],
190
+ contract_mode: 'advisory',
191
+ };
192
+ }
193
+
194
+ function sanitizeContractProfilePatch(input = {}) {
195
+ const errors = [];
196
+ const normalized = {};
197
+
198
+ if (Object.prototype.hasOwnProperty.call(input, 'archetype')) {
199
+ if (input.archetype == null || normalizeText(input.archetype) == null) {
200
+ normalized.archetype = null;
201
+ } else {
202
+ const archetype = normalizeArchetype(input.archetype);
203
+ if (!archetype) {
204
+ errors.push(`archetype must be one of: ${VALID_AGENT_ARCHETYPES.join(', ')}`);
205
+ } else {
206
+ normalized.archetype = archetype;
207
+ }
208
+ }
209
+ }
210
+
211
+ if (Object.prototype.hasOwnProperty.call(input, 'skills')) {
212
+ if (input.skills == null) {
213
+ normalized.skills = [];
214
+ } else if (!Array.isArray(input.skills) && typeof input.skills !== 'string') {
215
+ errors.push('skills must be a string or an array of strings');
216
+ } else if (typeof input.skills === 'string' && normalizeText(input.skills) == null) {
217
+ normalized.skills = [];
218
+ } else {
219
+ normalized.skills = normalizeSkills(input.skills);
220
+ }
221
+ }
222
+
223
+ if (Object.prototype.hasOwnProperty.call(input, 'contract_mode')) {
224
+ if (input.contract_mode == null || normalizeText(input.contract_mode) == null) {
225
+ normalized.contract_mode = 'advisory';
226
+ } else {
227
+ const contractMode = normalizeContractMode(input.contract_mode);
228
+ if (!contractMode) {
229
+ errors.push(`contract_mode must be one of: ${VALID_CONTRACT_MODES.join(', ')}`);
230
+ } else {
231
+ normalized.contract_mode = contractMode;
232
+ }
233
+ }
234
+ }
235
+
236
+ return {
237
+ valid: errors.length === 0,
238
+ errors,
239
+ normalized,
240
+ };
241
+ }
242
+
243
+ function resolveAgentContract(profile = {}) {
244
+ const rawRole = normalizeText(profile.role) || '';
245
+ const roleToken = normalizeRoleToken(profile.role);
246
+ const roleRecognized = roleToken ? KNOWN_ROLE_TOKENS.has(roleToken) : false;
247
+ const declaredArchetype = normalizeArchetype(profile.archetype);
248
+ const resolvedArchetype = declaredArchetype || inferArchetypeFromRole(roleToken);
249
+ const declaredSkills = normalizeSkills(profile.skills);
250
+ const registryEntry = resolvedArchetype ? AGENT_ARCHETYPE_REGISTRY[resolvedArchetype] : null;
251
+ const effectiveSkills = mergeSkills(declaredSkills, registryEntry ? registryEntry.default_skills : []);
252
+ const contractMode = normalizeContractMode(profile.contract_mode) || 'advisory';
253
+ const explicitMode = normalizeContractMode(profile.contract_mode);
254
+
255
+ let roleAlignment = 'none';
256
+ if (declaredArchetype && roleToken) {
257
+ if (registryEntry && registryEntry.compatible_roles.includes(roleToken)) {
258
+ roleAlignment = 'aligned';
259
+ } else if (roleRecognized) {
260
+ roleAlignment = 'mismatch';
261
+ } else {
262
+ roleAlignment = 'unknown_role';
263
+ }
264
+ } else if (declaredArchetype) {
265
+ roleAlignment = 'archetype_only';
266
+ } else if (roleToken) {
267
+ roleAlignment = 'legacy_role_only';
268
+ }
269
+
270
+ const hasExplicitContract = !!(
271
+ declaredArchetype
272
+ || declaredSkills.length > 0
273
+ || (explicitMode && explicitMode !== 'advisory')
274
+ );
275
+
276
+ return {
277
+ role: rawRole,
278
+ role_token: roleToken,
279
+ role_recognized: roleRecognized,
280
+ declared_archetype: declaredArchetype,
281
+ archetype: resolvedArchetype,
282
+ skills: declaredSkills,
283
+ effective_skills: effectiveSkills,
284
+ contract_mode: contractMode,
285
+ role_alignment: roleAlignment,
286
+ has_explicit_contract: hasExplicitContract,
287
+ has_advisory_context: hasExplicitContract || !!roleToken,
288
+ registry_entry: registryEntry,
289
+ };
290
+ }
291
+
292
+ function buildGuideContractAdvisory(contract) {
293
+ if (!contract || !contract.has_advisory_context) return null;
294
+
295
+ let status = 'legacy';
296
+ let summary;
297
+
298
+ if (contract.declared_archetype && contract.role_alignment === 'aligned') {
299
+ status = 'aligned';
300
+ summary = `Declared archetype "${contract.declared_archetype}" aligns with role "${contract.role || contract.role_token}".`;
301
+ } else if (contract.declared_archetype && contract.role_alignment === 'mismatch') {
302
+ status = 'mismatch';
303
+ summary = `Declared archetype "${contract.declared_archetype}" may be a weaker fit for role "${contract.role || contract.role_token}".`;
304
+ } else if (contract.declared_archetype && contract.role_alignment === 'unknown_role') {
305
+ status = 'legacy';
306
+ summary = `Declared archetype "${contract.declared_archetype}" is active, while role "${contract.role}" remains a compatibility-only free-form label.`;
307
+ } else if (contract.declared_archetype) {
308
+ status = 'aligned';
309
+ summary = `Declared archetype "${contract.declared_archetype}" is active in ${contract.contract_mode} mode.`;
310
+ } else {
311
+ summary = contract.role
312
+ ? `Legacy role "${contract.role}" remains active during contract migration.`
313
+ : `Contract mode is ${contract.contract_mode}.`;
314
+ }
315
+
316
+ const recommendation = contract.registry_entry
317
+ ? contract.registry_entry.summary
318
+ : 'Add an archetype and skills[] if you want more explicit advisory guidance.';
319
+
320
+ return {
321
+ status,
322
+ summary,
323
+ recommendation,
324
+ migration_note: contract.contract_mode === 'strict'
325
+ ? 'Strict intent is recorded, but this Task 11B slice keeps contract handling advisory outside existing managed-mode and evidence gates.'
326
+ : null,
327
+ };
328
+ }
329
+
330
+ function collectFitKeywords(contract, text) {
331
+ if (!contract || !text) return [];
332
+
333
+ const haystack = String(text).toLowerCase();
334
+ const candidates = mergeSkills(
335
+ contract.skills,
336
+ contract.effective_skills,
337
+ contract.registry_entry ? contract.registry_entry.keywords : []
338
+ );
339
+
340
+ const matches = [];
341
+ for (const token of candidates) {
342
+ if (token.length < 3) continue;
343
+ if (haystack.includes(token) && !matches.includes(token)) matches.push(token);
344
+ if (matches.length >= 4) break;
345
+ }
346
+ return matches;
347
+ }
348
+
349
+ function analyzeContractFit(contract, target = {}) {
350
+ if (!contract || !contract.has_advisory_context) return null;
351
+
352
+ const workType = normalizeWorkType(target.work_type) || 'task';
353
+ const title = normalizeText(target.title) || '';
354
+ const description = normalizeText(target.description) || '';
355
+ const text = `${title} ${description}`.trim();
356
+ const keywordMatches = collectFitKeywords(contract, text);
357
+ const registryEntry = contract.registry_entry;
358
+ const assigned = !!target.assigned;
359
+ let score = 0;
360
+
361
+ if (registryEntry && registryEntry.preferred_work_types.includes(workType)) score += 2;
362
+ if (registryEntry && registryEntry.discouraged_work_types.includes(workType)) score -= 2;
363
+ if (keywordMatches.length > 0) score += keywordMatches.length >= 2 ? 2 : 1;
364
+
365
+ let status = 'neutral';
366
+ if (score >= 2) status = 'aligned';
367
+ else if (score === 1) status = 'partial';
368
+ else if (score < 0) status = 'mismatch';
369
+
370
+ let summary;
371
+ if (status === 'aligned') {
372
+ summary = contract.archetype
373
+ ? `This ${workType.replace(/_/g, ' ')} fits your ${contract.archetype} contract.`
374
+ : 'This work fits your current legacy role guidance.';
375
+ } else if (status === 'partial') {
376
+ summary = contract.archetype
377
+ ? `This ${workType.replace(/_/g, ' ')} partially fits your ${contract.archetype} contract.`
378
+ : 'This work partially fits your current legacy role guidance.';
379
+ } else if (status === 'mismatch' && assigned) {
380
+ summary = contract.archetype
381
+ ? `This assigned work may be a weaker fit for your ${contract.archetype} contract, but assigned work still takes precedence in this advisory slice.`
382
+ : 'This assigned work may be a weaker fit for your current legacy role guidance, but assigned work still takes precedence in this advisory slice.';
383
+ } else if (status === 'mismatch') {
384
+ summary = contract.archetype
385
+ ? `This ${workType.replace(/_/g, ' ')} may be a weaker fit for your ${contract.archetype} contract.`
386
+ : 'This work may be a weaker fit for your current legacy role guidance.';
387
+ } else {
388
+ summary = contract.archetype
389
+ ? `No strong contract fit signal for this ${workType.replace(/_/g, ' ')}.`
390
+ : 'No strong contract fit signal for this work item.';
391
+ }
392
+
393
+ return {
394
+ status,
395
+ summary,
396
+ keyword_matches: keywordMatches,
397
+ recommendation: registryEntry ? registryEntry.summary : null,
398
+ migration_note: contract.contract_mode === 'strict' && status !== 'aligned'
399
+ ? 'Strict intent is recorded, but Task 11B surfaces this only as advisory guidance.'
400
+ : null,
401
+ };
402
+ }
403
+
404
+ function buildRuntimeContractMetadata(contract) {
405
+ if (!contract) {
406
+ return {
407
+ archetype: '',
408
+ skills: [],
409
+ contract_mode: 'advisory',
410
+ has_explicit_contract: false,
411
+ contract: null,
412
+ };
413
+ }
414
+
415
+ return {
416
+ archetype: contract.declared_archetype || '',
417
+ skills: contract.skills,
418
+ contract_mode: contract.contract_mode,
419
+ has_explicit_contract: contract.has_explicit_contract,
420
+ contract: {
421
+ archetype: contract.archetype || null,
422
+ declared_archetype: contract.declared_archetype || null,
423
+ role_token: contract.role_token || null,
424
+ role_alignment: contract.role_alignment,
425
+ skills: contract.skills,
426
+ effective_skills: contract.effective_skills,
427
+ contract_mode: contract.contract_mode,
428
+ has_explicit_contract: contract.has_explicit_contract,
429
+ },
430
+ };
431
+ }
432
+
433
+ module.exports = {
434
+ AGENT_ARCHETYPE_REGISTRY,
435
+ VALID_AGENT_ARCHETYPES,
436
+ VALID_CONTRACT_MODES,
437
+ analyzeContractFit,
438
+ buildGuideContractAdvisory,
439
+ buildRuntimeContractMetadata,
440
+ createDefaultContractMetadata,
441
+ normalizeArchetype,
442
+ normalizeContractMode,
443
+ normalizeRoleToken,
444
+ normalizeSkills,
445
+ resolveAgentContract,
446
+ sanitizeContractProfilePatch,
447
+ };