@aiwg/cli 2026.10.4 → 2026.10.6

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 (220) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +3 -1
  6. package/dist/src/artifacts/browser-export.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +8 -1
  8. package/dist/src/artifacts/cli.js +72 -9
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  10. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  11. package/dist/src/artifacts/graph-query.js +18 -8
  12. package/dist/src/artifacts/index-adapters.js +339 -0
  13. package/dist/src/artifacts/index-builder.js +146 -16
  14. package/dist/src/artifacts/index-files.js +15 -4
  15. package/dist/src/artifacts/index-preparation.js +91 -26
  16. package/dist/src/artifacts/index-reader.js +44 -0
  17. package/dist/src/artifacts/index-refresh.js +130 -56
  18. package/dist/src/artifacts/index-status.js +20 -5
  19. package/dist/src/artifacts/index-worker.mjs +9 -7
  20. package/dist/src/artifacts/memory-maintenance.js +503 -0
  21. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  22. package/dist/src/artifacts/query-engine.js +263 -108
  23. package/dist/src/artifacts/relations-builder.js +2 -2
  24. package/dist/src/artifacts/source-graph.js +2 -2
  25. package/dist/src/artifacts/types.js +9 -1
  26. package/dist/src/cli/handlers/decision.js +19 -1
  27. package/dist/src/cli/handlers/guided-setup.js +470 -0
  28. package/dist/src/cli/handlers/help.js +2 -2
  29. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  30. package/dist/src/cli/handlers/index.js +4 -2
  31. package/dist/src/cli/handlers/init.js +41 -6
  32. package/dist/src/cli/handlers/local-executor.js +5 -3
  33. package/dist/src/cli/handlers/mc.js +509 -53
  34. package/dist/src/cli/handlers/models.js +32 -2
  35. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  36. package/dist/src/cli/handlers/rules.js +1 -1
  37. package/dist/src/cli/handlers/script-runner.js +73 -2
  38. package/dist/src/cli/handlers/serve.js +5 -3
  39. package/dist/src/cli/handlers/sessions.js +40 -6
  40. package/dist/src/cli/handlers/setup.js +428 -1
  41. package/dist/src/cli/handlers/skill-lint.js +1 -1
  42. package/dist/src/cli/handlers/subcommands.js +118 -8
  43. package/dist/src/cli/handlers/use.js +279 -93
  44. package/dist/src/cli/handlers/workspace.js +3 -1
  45. package/dist/src/cli/prompt-utils.js +8 -0
  46. package/dist/src/cli/router.js +16 -1
  47. package/dist/src/config/aiwg-config.js +42 -0
  48. package/dist/src/config/cli.js +7 -3
  49. package/dist/src/config/decision-config.js +14 -1
  50. package/dist/src/config/native-sandbox-warnings.js +25 -0
  51. package/dist/src/contributors/validation.js +6 -5
  52. package/dist/src/decision/admission.js +7 -3
  53. package/dist/src/decision/benchmark.js +469 -0
  54. package/dist/src/decision/ledger.js +22 -3
  55. package/dist/src/decision/pricing.js +147 -0
  56. package/dist/src/decision/qualification/gate-evidence.js +18 -15
  57. package/dist/src/decision/qualification/load-staged.js +539 -0
  58. package/dist/src/decision/qualification/load.js +109 -33
  59. package/dist/src/decision/qualification/runner.js +5 -2
  60. package/dist/src/decision/review/service.js +6 -4
  61. package/dist/src/decision/review/validate.js +23 -2
  62. package/dist/src/decision/savings.js +8 -3
  63. package/dist/src/discovery/index.js +492 -0
  64. package/dist/src/discovery/interview.js +233 -0
  65. package/dist/src/discovery/plan-apply.js +571 -0
  66. package/dist/src/discovery/plan-store.js +171 -0
  67. package/dist/src/discovery/plan-types.js +2 -0
  68. package/dist/src/discovery/plan-validation.js +139 -0
  69. package/dist/src/discovery/types.js +2 -0
  70. package/dist/src/extensions/commands/definitions.js +49 -9
  71. package/dist/src/extensions/manifest.js +71 -44
  72. package/dist/src/extensions/project-local-discovery.js +1 -1
  73. package/dist/src/extensions/project-quickref.js +18 -17
  74. package/dist/src/extensions/validation.js +32 -29
  75. package/dist/src/housekeeper/daemon-config.js +148 -0
  76. package/dist/src/housekeeper/ipc.js +84 -0
  77. package/dist/src/housekeeper/lifecycle.js +148 -0
  78. package/dist/src/housekeeper/platform.js +35 -0
  79. package/dist/src/housekeeper/status.js +70 -0
  80. package/dist/src/housekeeper/units.js +114 -0
  81. package/dist/src/jobs/flow.js +22 -21
  82. package/dist/src/lint/rule-activation.js +20 -5
  83. package/dist/src/lint/runner.js +1 -1
  84. package/dist/src/mcp/cli.mjs +5 -6
  85. package/dist/src/mcp/config-file.mjs +6 -2
  86. package/dist/src/mcp/config-layers.mjs +23 -7
  87. package/dist/src/mcp/profiles.js +18 -31
  88. package/dist/src/mcp/profiles.mjs +18 -32
  89. package/dist/src/mcp/registry.js +7 -27
  90. package/dist/src/mcp/registry.mjs +7 -28
  91. package/dist/src/mcp/server.mjs +34 -1
  92. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  93. package/dist/src/mcp/tools/command-run.mjs +3 -2
  94. package/dist/src/mcp/tools/decision.mjs +49 -48
  95. package/dist/src/mcp/tools/discovery.mjs +46 -21
  96. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  97. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  98. package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
  99. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  100. package/dist/src/memory/audit.js +719 -0
  101. package/dist/src/memory/canonical-context.js +392 -13
  102. package/dist/src/memory/canonical-maintenance.js +95 -0
  103. package/dist/src/memory/cli-result.js +22 -0
  104. package/dist/src/memory/cli.js +176 -0
  105. package/dist/src/memory/context-pack.js +57 -6
  106. package/dist/src/memory/cost.js +512 -0
  107. package/dist/src/memory/firewall-classification.d.mts +7 -0
  108. package/dist/src/memory/firewall-classification.mjs +32 -0
  109. package/dist/src/memory/index.js +8 -0
  110. package/dist/src/memory/line-maintenance.js +68 -0
  111. package/dist/src/memory/maintenance-cli.js +286 -0
  112. package/dist/src/memory/maintenance-contract.js +136 -0
  113. package/dist/src/memory/maintenance-index.js +18 -0
  114. package/dist/src/memory/maintenance-owner.js +2 -0
  115. package/dist/src/memory/maintenance-record.js +138 -0
  116. package/dist/src/memory/maintenance.js +254 -0
  117. package/dist/src/memory/provider-memory-adapter.js +154 -0
  118. package/dist/src/memory/recommend-cli.js +83 -0
  119. package/dist/src/memory/recommend.js +269 -0
  120. package/dist/src/memory/scheduled.js +81 -0
  121. package/dist/src/memory/taxonomy.js +44 -0
  122. package/dist/src/mission-protocol/codecs.js +19 -9
  123. package/dist/src/models/config-loader.js +11 -1
  124. package/dist/src/models/model-capabilities.v1.json +74 -50
  125. package/dist/src/models/provider-policy.js +125 -62
  126. package/dist/src/models/resolver.js +45 -8
  127. package/dist/src/models/worker-installation.d.mts +7 -0
  128. package/dist/src/models/worker-installation.mjs +169 -37
  129. package/dist/src/models/wrapper-route.js +1 -1
  130. package/dist/src/output-modes/registry.js +8 -7
  131. package/dist/src/plugin/metadata-validator.js +1 -1
  132. package/dist/src/providers/deploy-version.d.mts +1 -0
  133. package/dist/src/providers/deploy-version.mjs +17 -0
  134. package/dist/src/providers/provider-definitions.js +19 -16
  135. package/dist/src/providers/skill-ownership.d.mts +11 -2
  136. package/dist/src/providers/skill-ownership.mjs +35 -17
  137. package/dist/src/rules/activation.d.mts +11 -2
  138. package/dist/src/rules/activation.mjs +82 -6
  139. package/dist/src/rules/inventory.js +1 -1
  140. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  141. package/dist/src/schema/zod-validation.d.mts +6 -0
  142. package/dist/src/schema/zod-validation.mjs +50 -0
  143. package/dist/src/serve/executor-registry.js +297 -20
  144. package/dist/src/serve/executor-token.d.mts +1 -0
  145. package/dist/src/serve/executor-token.mjs +8 -0
  146. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  147. package/dist/src/serve/worker-invocation.js +114 -15
  148. package/dist/src/sessions/adapters/claude.js +19 -18
  149. package/dist/src/sessions/adapters/codex.js +33 -19
  150. package/dist/src/sessions/adapters/copilot.js +14 -13
  151. package/dist/src/sessions/adapters/cursor.js +19 -18
  152. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  153. package/dist/src/sessions/adapters/factory.js +11 -10
  154. package/dist/src/sessions/adapters/generic.js +32 -23
  155. package/dist/src/sessions/adapters/grokbot.js +35 -35
  156. package/dist/src/sessions/adapters/hermes.js +20 -19
  157. package/dist/src/sessions/adapters/muse.js +50 -49
  158. package/dist/src/sessions/adapters/omp.js +5 -4
  159. package/dist/src/sessions/adapters/openclaw.js +30 -29
  160. package/dist/src/sessions/adapters/opencode.js +26 -26
  161. package/dist/src/sessions/adapters/openhuman.js +24 -23
  162. package/dist/src/sessions/adapters/pi.js +5 -4
  163. package/dist/src/sessions/adapters/windsurf.js +8 -8
  164. package/dist/src/sessions/analytics.js +10 -9
  165. package/dist/src/sessions/batch-contracts.js +1 -1
  166. package/dist/src/sessions/batch-import.js +202 -2
  167. package/dist/src/sessions/candidates.js +7 -6
  168. package/dist/src/sessions/contracts.js +73 -54
  169. package/dist/src/sessions/coverage-preflight.js +26 -1
  170. package/dist/src/sessions/output-registration.js +11 -10
  171. package/dist/src/sessions/policy.js +18 -7
  172. package/dist/src/sessions/refresh.js +2 -3
  173. package/dist/src/sessions/repository.js +4 -2
  174. package/dist/src/sessions/workspace-discovery.js +92 -26
  175. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  176. package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
  177. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  178. package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
  179. package/dist/src/update/checker.d.mts +4 -0
  180. package/dist/src/update/checker.mjs +14 -0
  181. package/dist/src/writing/exemplar-selection.js +6 -6
  182. package/dist/src/writing/voice-evaluation.js +24 -23
  183. package/dist/src/writing/writer-migration.js +14 -13
  184. package/dist/src/writing/writer-profile.js +19 -18
  185. package/dist/src/writing/writing-brief.js +18 -17
  186. package/dist/src/writing/writing-receipt.js +47 -46
  187. package/package.json +5 -2
  188. package/schemas/decision/DecisionReview.schema.json +78 -1
  189. package/schemas/mission-v1.schema.json +3 -0
  190. package/tools/agents/deploy-agents.mjs +67 -39
  191. package/tools/agents/providers/antigravity.mjs +6 -1
  192. package/tools/agents/providers/base.mjs +19 -13
  193. package/tools/agents/providers/claude.mjs +1 -1
  194. package/tools/agents/providers/codex.mjs +50 -56
  195. package/tools/agents/providers/copilot.mjs +8 -2
  196. package/tools/agents/providers/cursor.mjs +1 -1
  197. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  198. package/tools/agents/providers/factory.mjs +7 -1
  199. package/tools/agents/providers/grok-build.mjs +18 -2
  200. package/tools/agents/providers/grokbot.mjs +1 -1
  201. package/tools/agents/providers/hermes.mjs +7 -13
  202. package/tools/agents/providers/muse-hooks.mjs +21 -14
  203. package/tools/agents/providers/muse.mjs +7 -2
  204. package/tools/agents/providers/omp.mjs +28 -4
  205. package/tools/agents/providers/opencode.mjs +14 -5
  206. package/tools/agents/providers/openhuman.mjs +1 -1
  207. package/tools/agents/providers/pi.mjs +1 -1
  208. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  209. package/tools/agents/providers/rule-delivery.mjs +263 -71
  210. package/tools/agents/providers/windsurf.mjs +7 -0
  211. package/tools/cli/workspace-status.mjs +5 -0
  212. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  213. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  214. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  215. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  216. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  217. package/tools/ralph-external/process-monitor.mjs +7 -4
  218. package/tools/ralph-external/session-launcher.mjs +280 -32
  219. package/tools/skills/deploy-skills-codex.mjs +11 -7
  220. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -0,0 +1,269 @@
1
+ import { z } from 'zod';
2
+ import { integer, dateTime, number } from '../schema/zod-validation.mjs';
3
+ import { costMemory, CostConfidenceSchema } from './cost.js';
4
+ import { sha256 } from './context-pack.js';
5
+ import { MemoryDigestSchema, MemoryRecordSchema } from './maintenance-record.js';
6
+ const Text = z.string().min(1);
7
+ const DateTime = dateTime();
8
+ /** Local starting points for workload evaluation, not research-established thresholds. */
9
+ export const RecommendationThresholdsSchema = z.strictObject({
10
+ minimumAgeDays: integer().nonnegative().default(30),
11
+ reviewAgeDays: integer().nonnegative().default(90),
12
+ demoteMinimumTokens: integer().nonnegative().default(512),
13
+ taskPackCharacters: integer().min(256).max(65536).default(8000),
14
+ });
15
+ const ItemEvidenceSchema = z.strictObject({
16
+ source: Text,
17
+ criticality: z.enum(['ordinary', 'critical']).optional(),
18
+ ownerPinned: z.boolean().optional(),
19
+ rareObligation: z.boolean().optional(),
20
+ provenance: z.enum(['verified', 'uncertain', 'conflicting']).optional(),
21
+ createdAt: DateTime.optional(), reviewedAt: DateTime.optional(),
22
+ history: z.enum(['known', 'unknown']).optional(),
23
+ obsolete: z.boolean().optional(),
24
+ supersededBy: Text.optional(), conflictsWith: z.array(Text).optional(),
25
+ categoryCorrection: Text.optional(), summaryStale: z.boolean().optional(),
26
+ residentProposal: z.strictObject({
27
+ owner: Text.optional(), rationale: Text.optional(), budgetTokens: integer().positive().optional(),
28
+ reviewDate: DateTime.optional(), boundedPackEvaluated: z.boolean().optional(),
29
+ }).optional(),
30
+ });
31
+ export const RecommendationInputsSchema = z.strictObject({
32
+ thresholds: RecommendationThresholdsSchema.prefault({}),
33
+ items: z.record(z.string(), ItemEvidenceSchema).prefault({}),
34
+ });
35
+ const CandidateSchema = z.strictObject({
36
+ id: Text,
37
+ kind: z.enum(['merge', 'recategorize', 'conflict', 'supersede', 'demote', 'promote', 'retire']),
38
+ target: MemoryRecordSchema,
39
+ sourceDigest: MemoryDigestSchema.nullable(),
40
+ related: z.array(Text),
41
+ relatedTargets: z.array(z.strictObject({ record: MemoryRecordSchema, sourceDigest: MemoryDigestSchema.nullable() })),
42
+ status: z.enum(['advisory', 'blocked', 'rejected', 'conflict']),
43
+ advisoryRank: integer().positive(),
44
+ rationale: Text,
45
+ evidence: z.array(z.strictObject({ source: Text, detail: Text })).min(1),
46
+ uncertainty: z.array(Text).min(1),
47
+ blockedReasons: z.array(Text), protectedItemExceptions: z.array(Text),
48
+ expectedSavings: z.strictObject({
49
+ residentTokens: number().nullable(), estimator: Text, confidence: CostConfidenceSchema, basis: Text,
50
+ }),
51
+ retrievalImpact: z.strictObject({
52
+ detail: Text, requiresVerification: z.literal(true),
53
+ proposedPlacement: z.enum(['indexed', 'task-context', 'resident']).nullable(),
54
+ category: Text.nullable(),
55
+ taskPack: z.strictObject({ builder: z.literal('buildWorkspaceContextPack'), totalCharacters: integer().positive() }),
56
+ }),
57
+ residentJustification: z.strictObject({
58
+ owner: Text.optional(), rationale: Text.optional(), budgetTokens: integer().positive().optional(),
59
+ reviewDate: DateTime.optional(), boundedPackEvaluated: z.boolean().optional(),
60
+ }).nullable(),
61
+ });
62
+ export const MemoryRecommendationsSchema = z.strictObject({
63
+ schemaVersion: z.literal('aiwg.memory-recommendations.v1'),
64
+ at: DateTime, workspace: Text, scope: Text,
65
+ advisoryOnly: z.literal(true), defaultPlacement: z.literal('indexed'),
66
+ thresholds: RecommendationThresholdsSchema,
67
+ coverage: z.strictObject({ complete: z.boolean(), limitsHit: z.array(Text), exclusions: z.array(Text) }),
68
+ candidates: z.array(CandidateSchema),
69
+ notes: z.array(Text),
70
+ });
71
+ /** Pure policy over audit + cost evidence. No file, provider or telemetry writes. */
72
+ export function recommendMemory(audit, input = {}, costOptions = {}) {
73
+ const config = RecommendationInputsSchema.parse(input);
74
+ const at = costOptions.at ?? audit.at;
75
+ const now = Date.parse(DateTime.parse(at));
76
+ const cost = costMemory(audit, { ...costOptions, at,
77
+ demote: audit.items.filter(i => i.record?.placement === 'resident').map(i => i.id) });
78
+ const costs = new Map(cost.items.map(i => [i.id, i]));
79
+ const items = new Map(audit.items.map(i => [i.id, i]));
80
+ for (const id of Object.keys(config.items))
81
+ if (!items.has(id))
82
+ throw new Error(`Recommendation evidence target is outside the audit inventory: ${id}`);
83
+ const candidates = [];
84
+ const age = (date) => date ? (now - Date.parse(date)) / 86400000 : null;
85
+ const metadata = (id) => config.items[id];
86
+ const protections = (id) => {
87
+ const record = items.get(id)?.record;
88
+ const info = metadata(id);
89
+ return [
90
+ ...(record?.authority.protection === 'pinned' || info?.ownerPinned ? ['Owner pin protects this item'] : []),
91
+ ...(info?.rareObligation ? ['Rare obligation must remain available'] : []),
92
+ ...(info?.criticality === 'critical' ? ['Critical rule requires explicit owner disposition'] : []),
93
+ ];
94
+ };
95
+ const conflicts = new Set();
96
+ for (const finding of audit.findings)
97
+ if (/conflict|collision/.test(finding.code)) {
98
+ for (const item of audit.items)
99
+ if (finding.paths.includes(item.sourcePath) || item.exposures.some(e => finding.paths.includes(e.path)))
100
+ conflicts.add(item.id);
101
+ }
102
+ for (const [id, info] of Object.entries(config.items)) {
103
+ if (info.provenance === 'conflicting' || info.conflictsWith?.length) {
104
+ conflicts.add(id);
105
+ for (const related of info.conflictsWith ?? [])
106
+ if (items.has(related))
107
+ conflicts.add(related);
108
+ }
109
+ }
110
+ function add(id, kind, rationale, related = [], reasons = [], placement = null) {
111
+ const item = items.get(id);
112
+ if (!item.record)
113
+ return;
114
+ const info = metadata(id);
115
+ const attribution = costs.get(id);
116
+ const exceptions = [...new Set([id, ...related].flatMap(protections))];
117
+ const blocked = [...reasons];
118
+ const destructive = ['retire', 'demote', 'merge', 'supersede'].includes(kind);
119
+ if (destructive)
120
+ blocked.push(...exceptions);
121
+ if (kind !== 'conflict' && conflicts.has(id))
122
+ blocked.push('Conflicting source evidence needs owner resolution before changes');
123
+ if (destructive && item.record.lifecycle !== 'active')
124
+ blocked.push('Only active originals are eligible');
125
+ let tokens = null;
126
+ let confidence = 'unknown';
127
+ let basis = 'No supported saving estimate; retained history and live activation are unmeasured';
128
+ if (kind === 'demote') {
129
+ const preview = cost.demotionPreview.targets.find(t => t.id === id);
130
+ tokens = attribution.tokens === null ? null : attribution.tokens - preview.after.anchorTokens;
131
+ confidence = tokens === null ? 'unknown' : 'inferred';
132
+ basis = 'Conditional static resident bytes minus cost.ts retrieval anchors; provider regeneration and retrieval proof still required';
133
+ }
134
+ candidates.push({
135
+ id: sha256(JSON.stringify({ kind, id, related: [...related].sort(), placement, category: info?.categoryCorrection ?? null })),
136
+ kind, target: item.record, sourceDigest: item.sourceDigest ?? item.digest, related,
137
+ relatedTargets: related.flatMap(key => {
138
+ const input = items.get(key);
139
+ return input?.record ? [{ record: input.record, sourceDigest: input.sourceDigest ?? input.digest }] : [];
140
+ }),
141
+ status: kind === 'conflict' ? 'conflict' : blocked.length ? 'blocked' : 'advisory', advisoryRank: 1,
142
+ rationale, evidence: [
143
+ { source: item.observation.evidence.source, detail: `Audit: ${item.observation.status}; ${item.category}; ${item.record.placement}; review ${item.reviewStatus}` },
144
+ { source: attribution.estimator, detail: `Cost ${attribution.tokens ?? 'unknown'} tokens (${attribution.confidence}); utility ${attribution.utility.outcome}; evidence ${attribution.utility.evidence ?? 'unknown'}` },
145
+ ...(info ? [{ source: info.source, detail: `Owner evidence: provenance ${info.provenance ?? 'unknown'}; history ${info.history ?? 'unknown'}; created ${info.createdAt ?? 'unknown'}; reviewed ${info.reviewedAt ?? 'unknown'}; obsolete ${info.obsolete ?? 'unknown'}` }] : []),
146
+ ...audit.findings.filter(f => f.paths.includes(item.sourcePath)).map(f => ({ source: f.code, detail: f.detail })),
147
+ ],
148
+ uncertainty: ['Static inventory does not prove live loading, successful retrieval, or task outcomes',
149
+ ...(!info?.createdAt ? ['Creation age is unknown; filesystem recency is not creation history'] : []),
150
+ ...(info?.provenance !== 'verified' ? ['Provenance is unverified or conflicting'] : []),
151
+ ...(!audit.coverage.complete ? ['Audit coverage is incomplete; absence is not evidence of disuse'] : [])],
152
+ blockedReasons: [...new Set(blocked)], protectedItemExceptions: exceptions,
153
+ expectedSavings: { residentTokens: tokens, estimator: attribution.estimator, confidence, basis },
154
+ retrievalImpact: { detail: kind === 'retire' ? 'Exclude from ordinary retrieval; preserve source, tombstone and explicit history/restore lookup'
155
+ : kind === 'conflict' || kind === 'supersede' ? 'Review source revisions and invalidate stale dependent summaries; never silently merge claims'
156
+ : 'Preserve source identity and authority; verify indexed discovery and bounded task-pack recall before resident removal',
157
+ requiresVerification: true, proposedPlacement: placement, category: kind === 'recategorize' ? info?.categoryCorrection ?? null : null,
158
+ taskPack: { builder: 'buildWorkspaceContextPack', totalCharacters: config.thresholds.taskPackCharacters } },
159
+ residentJustification: placement === 'resident' ? info?.residentProposal ?? null : null,
160
+ });
161
+ }
162
+ const seenDigests = new Set();
163
+ for (const item of [...audit.items].sort((a, b) => a.id.localeCompare(b.id))) {
164
+ if (!item.record)
165
+ continue;
166
+ const id = item.id;
167
+ const record = item.record;
168
+ const info = metadata(id);
169
+ const attribution = costs.get(id);
170
+ const createdAge = age(info?.createdAt);
171
+ const reviewAge = age(info?.reviewedAt);
172
+ if (conflicts.has(id))
173
+ add(id, 'conflict', 'Conflicting claims or owner metadata need explicit source review', info?.conflictsWith ?? []);
174
+ if (info?.categoryCorrection)
175
+ add(id, 'recategorize', 'Owner evidence identifies a taxonomy correction; preserve placement, lifecycle and authority');
176
+ if (info?.summaryStale || info?.supersededBy || ['stale', 'superseded'].includes(item.trust)
177
+ || item.reviewStatus === 'deployed-drift' || record.supersedes.some(handle => !items.has(handle) || items.get(handle)?.record?.lifecycle === 'active')) {
178
+ const related = info?.supersededBy ? [info.supersededBy] : record.supersedes;
179
+ add(id, 'supersede', 'Review supersession links and stale derived summaries against original source evidence', related, ['A reviewer must supply the replacement and validate lineage; no generated summary is accepted as a source fact']);
180
+ }
181
+ if (record.lifecycle !== 'active')
182
+ continue;
183
+ if (attribution.duplicateGroup && !seenDigests.has(attribution.duplicateGroup)) {
184
+ seenDigests.add(attribution.duplicateGroup);
185
+ const copies = audit.items.filter(i => i.digest === item.digest && i.record && i.id !== id);
186
+ const related = copies.map(i => i.id);
187
+ const incompatible = copies.some(i => i.record.scope !== record.scope || JSON.stringify(i.record.authority) !== JSON.stringify(record.authority));
188
+ add(id, 'merge', 'Identical content digests support consolidation review with immutable originals and source links retained', related, [...(incompatible ? ['Duplicate copies differ in scope or authority; no implicit merge is allowed'] : []),
189
+ ...(related.some(key => conflicts.has(key)) ? ['A duplicate has conflicting source evidence; resolve the conflict first'] : []),
190
+ ...(copies.some(copy => copy.record.lifecycle !== 'active') ? ['Duplicate inputs include inactive originals; review retained history separately'] : [])]);
191
+ }
192
+ if (!attribution.duplicateGroup && audit.findings.some(f => f.code === 'duplicate-resident-copy'
193
+ && (f.paths.includes(item.sourcePath) || item.exposures.some(e => f.paths.includes(e.path))))) {
194
+ add(id, 'merge', 'Multiple physical resident copies share one immutable identity; consolidate exposure paths while retaining the canonical source', [], ['Copies already share one record identity; use the existing deployment/regeneration owner rather than merging a record with itself']);
195
+ }
196
+ if (record.placement === 'resident' && (attribution.tokens === null || attribution.tokens >= config.thresholds.demoteMinimumTokens)) {
197
+ add(id, 'demote', 'Review resident footprint against observed utility; keep indexed retrieval and prefer a bounded task pack', [], [...(attribution.utility.used !== false || !attribution.utility.evidence ? ['No explicit evidence of unused resident context'] : []),
198
+ ...(info?.provenance !== 'verified' ? ['Verify provenance before demotion'] : [])], 'indexed');
199
+ }
200
+ if (record.placement === 'indexed' && attribution.utility.used === true && attribution.utility.evidence) {
201
+ add(id, 'promote', 'Successful-use evidence supports evaluating a bounded task pack before persistent residency', [], [], 'task-context');
202
+ }
203
+ if (info?.residentProposal) {
204
+ const proposal = info.residentProposal;
205
+ const reasons = [
206
+ ...(!proposal.owner || proposal.owner !== record.authority.owner ? ['Always-loaded placement requires the existing owner'] : []),
207
+ ...(!proposal.rationale ? ['Always-loaded placement requires a continuing rationale'] : []),
208
+ ...(!proposal.budgetTokens ? ['Always-loaded placement requires a token budget'] : []),
209
+ ...(!proposal.reviewDate || Date.parse(proposal.reviewDate) <= now ? ['Always-loaded placement requires a future review date'] : []),
210
+ ...(attribution.tokens === null || !proposal.budgetTokens || attribution.tokens > proposal.budgetTokens ? ['Known footprint must fit the resident budget'] : []),
211
+ ...(proposal.boundedPackEvaluated !== true ? ['Evaluate buildWorkspaceContextPack before persistent resident placement'] : []),
212
+ ...(attribution.utility.used !== true || !attribution.utility.evidence ? ['Observed successful use is required'] : []),
213
+ ...(info.provenance !== 'verified' ? ['Verify provenance before resident placement'] : []),
214
+ ...(record.placement === 'resident' ? ['Item is already resident'] : []),
215
+ ];
216
+ add(id, 'promote', 'Explicit always-loaded proposal requires owner, rationale, budget, review date and prior bounded-pack evaluation', [], reasons, 'resident');
217
+ const candidate = candidates.at(-1);
218
+ if (candidate.blockedReasons.length)
219
+ candidate.status = 'rejected';
220
+ }
221
+ // An old review opens investigation, never establishes retirement eligibility.
222
+ if (info?.obsolete || info?.supersededBy || (reviewAge !== null && reviewAge >= config.thresholds.reviewAgeDays)
223
+ || attribution.utility.rejected === true || (attribution.utility.used === false && Boolean(attribution.utility.evidence))) {
224
+ add(id, 'retire', 'Review obsolete or superseded material; recency and frequency alone cannot justify retirement', [], [
225
+ ...(info?.history !== 'known' ? ['Unknown history blocks retirement'] : []),
226
+ ...(createdAge === null ? ['Unknown creation age blocks retirement'] : createdAge < config.thresholds.minimumAgeDays ? ['Freshly created notes are protected from retirement'] : []),
227
+ ...(reviewAge !== null && reviewAge < 0 ? ['Review timestamp is in the future'] : []),
228
+ ...(!info?.obsolete && !info?.supersededBy ? ['Age, frequency and rejection alone are insufficient; establish obsolete or superseded source evidence'] : []),
229
+ ...(info?.provenance !== 'verified' ? ['Verified provenance is required for retirement'] : []),
230
+ ...(attribution.utility.used === null || !attribution.utility.evidence ? ['Observed utility history is unknown'] : []),
231
+ ...(attribution.utility.used === true ? ['Successful use contradicts retirement; resolve source evidence'] : []),
232
+ ...(info?.supersededBy && !items.has(info.supersededBy) ? ['Successor is outside this inventory; verify retrieval and scope'] : []),
233
+ ...(info?.supersededBy === id ? ['An item cannot supersede itself'] : []),
234
+ ...(info?.supersededBy && items.has(info.supersededBy)
235
+ && (items.get(info.supersededBy)?.record?.lifecycle !== 'active' || items.get(info.supersededBy)?.record?.scope !== record.scope)
236
+ ? ['Successor must be active and retrievable in the same scope'] : []),
237
+ ]);
238
+ }
239
+ }
240
+ const priority = { conflict: 0, recategorize: 1, supersede: 2, merge: 3, demote: 4, promote: 5, retire: 6 };
241
+ candidates.sort((a, b) => priority[a.kind] - priority[b.kind]
242
+ || (b.expectedSavings.residentTokens ?? -1) - (a.expectedSavings.residentTokens ?? -1) || a.id.localeCompare(b.id));
243
+ candidates.forEach((candidate, index) => { candidate.advisoryRank = index + 1; });
244
+ return MemoryRecommendationsSchema.parse({
245
+ schemaVersion: 'aiwg.memory-recommendations.v1', at, workspace: audit.workspace, scope: audit.scope,
246
+ advisoryOnly: true, defaultPlacement: 'indexed', thresholds: config.thresholds,
247
+ coverage: { complete: audit.coverage.complete, limitsHit: audit.coverage.limitsHit, exclusions: audit.coverage.exclusions },
248
+ candidates, notes: [
249
+ 'Ranking is advisory: conflicts, taxonomy, lineage, duplicates, demotion, promotion, retirement; savings only break ties. No approval or mutation is produced.',
250
+ 'Thresholds are configurable local starting points to evaluate per workload, not research-established values.',
251
+ 'New knowledge defaults to indexed storage; evaluate buildWorkspaceContextPack with the stated character budget before resident placement.',
252
+ 'Owner plan validation, exact source review, retrieval verification and provider regeneration remain required. Savings are conditional and are not additive across overlapping candidates.',
253
+ ],
254
+ });
255
+ }
256
+ export function formatMemoryRecommendations(report) {
257
+ return [`Memory recommendations (${report.scope}): ${report.workspace}; advisory only`,
258
+ ...report.candidates.flatMap(c => [
259
+ `${c.advisoryRank}. ${c.kind} ${c.target.sourcePath}${c.retrievalImpact.proposedPlacement ? ` -> ${c.retrievalImpact.proposedPlacement}` : ''} [${c.status}] ${c.id}`,
260
+ ` ${c.rationale}; resident savings ${c.expectedSavings.residentTokens ?? 'unknown'} tokens (${c.expectedSavings.confidence})`,
261
+ ...c.evidence.map(e => ` Evidence: ${e.source}: ${e.detail}`),
262
+ ...c.blockedReasons.map(reason => ` Blocked: ${reason}`),
263
+ ...c.protectedItemExceptions.map(reason => ` Protected: ${reason}`),
264
+ ` Retrieval: ${c.retrievalImpact.detail}; bounded pack ${c.retrievalImpact.taskPack.totalCharacters} characters`,
265
+ ...(c.residentJustification ? [` Resident justification: ${JSON.stringify(c.residentJustification)}`] : []),
266
+ ` Uncertainty: ${c.uncertainty.join('; ')}`,
267
+ ]), ...report.notes].join('\n');
268
+ }
269
+ //# sourceMappingURL=recommend.js.map
@@ -0,0 +1,81 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { projectAiwgWritePath } from '../config/project-artifacts.js';
5
+ import { auditMemory } from './audit.js';
6
+ import { recommendMemory, RecommendationInputsSchema } from './recommend.js';
7
+ import { parseMemoryAuditOptions, resolveMemoryCostInputs } from './cli.js';
8
+ import { createMaintenanceOwners } from './maintenance-cli.js';
9
+ import { applyMaintenancePlan, MaintenanceApplyError, maintenanceOperationKey, maintenancePath, maintenancePlanDigest, previewMaintenancePlan, verifyMaintenancePlan } from './maintenance.js';
10
+ import { MaintenancePlanSchema } from './maintenance-contract.js';
11
+ /** Read-only owner views; the routed report is the only payload written. */
12
+ export async function reportScheduledMemory(kind, args, workspace, at) {
13
+ const options = parseMemoryAuditOptions(['scope', 'provider'].flatMap(key => args[key] ? [`--${key}`, args[key]] : []));
14
+ // Resolve before scanning: an unavailable external destination must fail closed.
15
+ const reportPath = projectAiwgWritePath(workspace, 'reports', 'memory', `${kind}-${randomUUID()}.json`);
16
+ const audit = await auditMemory({ ...options, workspace, at });
17
+ const report = kind === 'memory.recommend'
18
+ ? recommendMemory(audit, args.evidence ? RecommendationInputsSchema.parse(JSON.parse(readFileSync(resolve(workspace, args.evidence), 'utf8'))) : {}, await resolveMemoryCostInputs({ demote: [], usage: args.usage ? resolve(workspace, args.usage) : undefined }))
19
+ : audit;
20
+ mkdirSync(dirname(reportPath), { recursive: true });
21
+ writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
22
+ const candidates = 'candidates' in report ? report.candidates.length : 0;
23
+ const findings = audit.findings.length;
24
+ // Findings are a normal report outcome, not a job failure: a non-zero exit
25
+ // would make the scheduler retry an unchanged read-only report.
26
+ return { exitStatus: 0,
27
+ summary: `${kind}: ${audit.items.length} items, ${findings} findings, ${candidates} recommendations`,
28
+ detail: { schemaVersion: report.schemaVersion, reportPath, counts: { items: audit.items.length, findings, candidates },
29
+ findingsStatus: findings || candidates ? 'findings' : 'clear', complete: report.coverage.complete } };
30
+ }
31
+ /** Scheduling executes one exact reviewed plan. It never prepares or resumes one. */
32
+ export async function applyScheduledMemory(args, root) {
33
+ try {
34
+ const plan = MaintenancePlanSchema.parse(JSON.parse(readFileSync(maintenancePath(root, args.plan), 'utf8')));
35
+ if (maintenancePlanDigest(plan) !== args.confirmedDigest)
36
+ throw new Error('Recorded plan digest mismatch; review and register the exact plan again');
37
+ const owners = await createMaintenanceOwners(root);
38
+ const receipts = await Promise.all(plan.operations.map(async (operation, index) => {
39
+ const owner = owners.find(value => value.name === operation.owner?.name);
40
+ if (!owner)
41
+ throw new Error('Unavailable maintenance owner');
42
+ const receipt = await owner.receipt(maintenanceOperationKey(plan, index), { readOnly: true });
43
+ if (receipt === undefined && await owner.pending?.(operation))
44
+ throw new Error('Interrupted owner transaction requires manual recovery');
45
+ return receipt;
46
+ }));
47
+ const completed = receipts.filter(receipt => receipt !== undefined).length;
48
+ if (completed && completed !== receipts.length)
49
+ throw new Error('Partial plan requires manual owner recovery; scheduling cannot replay it');
50
+ // Refuse invalid previews before even creating a coordinator journal.
51
+ // Apply repeats these checks, including the owners' checks under lock.
52
+ if (!completed)
53
+ await previewMaintenancePlan(plan, owners);
54
+ let applied = null;
55
+ let applyError;
56
+ let applyFailed = false;
57
+ if (completed !== receipts.length) {
58
+ try {
59
+ applied = await applyMaintenancePlan(plan, { root, owners, confirmedDigest: args.confirmedDigest });
60
+ }
61
+ catch (error) {
62
+ applyFailed = true;
63
+ applyError = error;
64
+ if (error instanceof MaintenanceApplyError)
65
+ applied = error.receipt;
66
+ }
67
+ }
68
+ const verification = await verifyMaintenancePlan(plan, owners);
69
+ if (applyFailed || applied?.partial || verification.findings.length) {
70
+ return { exitStatus: 1, uncertain: true, summary: `Memory apply requires inspection of partial results or verification findings${applyError instanceof Error ? `: ${applyError.message}` : ''}`,
71
+ detail: { applied, verification } };
72
+ }
73
+ return { exitStatus: 0, summary: applied ? 'Reviewed memory plan applied and verified' : 'Reviewed memory plan already applied and verified; no changes',
74
+ detail: { planDigest: args.confirmedDigest, alreadyApplied: applied === null, applied, verification } };
75
+ }
76
+ catch (error) {
77
+ return { exitStatus: 1, uncertain: true, summary: `Memory apply refused; inspect before recovery: ${error instanceof Error ? error.message : String(error)}`,
78
+ ...(error instanceof MaintenanceApplyError ? { detail: { applied: error.receipt } } : {}) };
79
+ }
80
+ }
81
+ //# sourceMappingURL=scheduled.js.map
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+ import { dateTime } from '../schema/zod-validation.mjs';
3
+ export const MemoryScopeSchema = z.enum(['global', 'project', 'provider-native', 'loaded/session']);
4
+ export const MemoryPlacementSchema = z.enum(['resident', 'indexed', 'task-context', 'archived']);
5
+ export const MemoryLifecycleSchema = z.enum(['active', 'superseded', 'retired', 'quarantined', 'purged']);
6
+ export const MemoryAuthoritySchema = z.strictObject({
7
+ owner: z.string().min(1),
8
+ source: z.enum(['operator', 'reviewer', 'import', 'generated-output-proposed']),
9
+ protection: z.enum(['pinned', 'unprotected']),
10
+ });
11
+ export const MemoryObservationSchema = z.strictObject({
12
+ status: z.enum(['observed', 'inferred', 'on-disk', 'absent', 'inaccessible', 'opaque', 'unknown']),
13
+ evidence: z.strictObject({
14
+ source: z.string().min(1),
15
+ at: dateTime(),
16
+ session: z.string().min(1).nullable(),
17
+ provider: z.string().min(1).nullable(),
18
+ version: z.string().min(1).nullable(),
19
+ }),
20
+ }).superRefine((observation, context) => {
21
+ if (observation.status === 'observed'
22
+ && (!observation.evidence.session || !observation.evidence.provider || !observation.evidence.version)) {
23
+ context.addIssue({ code: 'custom', message: 'Observed live context requires session, provider and version evidence' });
24
+ }
25
+ });
26
+ export const MemoryTaxonomySchema = z.strictObject({
27
+ schemaVersion: z.literal('aiwg.memory-taxonomy.v1'),
28
+ scope: MemoryScopeSchema,
29
+ placement: MemoryPlacementSchema,
30
+ lifecycle: MemoryLifecycleSchema,
31
+ authority: MemoryAuthoritySchema,
32
+ observation: MemoryObservationSchema,
33
+ });
34
+ /** A taxonomy transition changes exactly the selected axis; it grants no mutation authority. */
35
+ export function transitionMemoryAxis(item, axis, value) {
36
+ const schemas = {
37
+ scope: MemoryScopeSchema,
38
+ placement: MemoryPlacementSchema,
39
+ lifecycle: MemoryLifecycleSchema,
40
+ authority: MemoryAuthoritySchema,
41
+ };
42
+ return { ...item, [axis]: schemas[axis].parse(value) };
43
+ }
44
+ //# sourceMappingURL=taxonomy.js.map
@@ -158,9 +158,9 @@ export function validateCanonicalMission(value) {
158
158
  throw new Error('Canonical Mission artifacts must be an array.');
159
159
  return structuredClone(value);
160
160
  }
161
- export function decodeMission(input, source) {
161
+ export function decodeMission(input, source, workerConsumer) {
162
162
  const record = object(input);
163
- assertMissionWorkerHandoff(record, source);
163
+ assertMissionWorkerHandoff(record, source, workerConsumer);
164
164
  if (source === 'canonical') {
165
165
  const value = validateCanonicalMission(record);
166
166
  return { value, sourceVersion: MISSION_API_VERSION, warnings: [], preservedExtensions: structuredClone(value.extensions ?? {}), lossReport: [] };
@@ -227,7 +227,7 @@ function projectionLosses(value, target) {
227
227
  return losses;
228
228
  }
229
229
  export function encodeMission(value, target, options = {}) {
230
- assertMissionWorkerHandoff(value, target);
230
+ assertMissionWorkerHandoff(value, target, options.workerConsumer);
231
231
  const mission = validateCanonicalMission(value);
232
232
  if (target === 'canonical')
233
233
  return { value: mission, targetVersion: MISSION_API_VERSION, warnings: [], lossReport: [] };
@@ -512,9 +512,17 @@ function gateRequest(gate) {
512
512
  bundleDigest: gate.bundleDigest ?? workerDefinitionBundleDigest(),
513
513
  };
514
514
  }
515
+ /** Build registration metadata for the features explicitly qualified by an owner. */
516
+ export function workerConsumerSupport(features) {
517
+ for (const feature of features)
518
+ if (!WORKER_KNOWN_FEATURES.includes(feature)) {
519
+ throw new WorkerContractError('OG_CONTRACT_UNSUPPORTED', `OG_CONTRACT_UNSUPPORTED: unknown consumer feature '${feature}'.`);
520
+ }
521
+ return { revision: WORKER_CONTRACT_REVISION, bundleDigest: workerDefinitionBundleDigest(), features: [...features], lossless: true };
522
+ }
515
523
  /** Trusted registration for the codec only; runtime consumers remain unqualified. */
516
524
  function canonicalWorkerSupport() {
517
- return { revision: WORKER_CONTRACT_REVISION, bundleDigest: workerDefinitionBundleDigest(), features: [...WORKER_KNOWN_FEATURES], lossless: true };
525
+ return workerConsumerSupport(WORKER_KNOWN_FEATURES);
518
526
  }
519
527
  function workerDispatchRecord(value) {
520
528
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
@@ -524,10 +532,10 @@ function workerDispatchRecord(value) {
524
532
  }
525
533
  /**
526
534
  * Existing Mission/native owners call this before cloning, projection or launch.
527
- * Only the canonical codec has qualified worker preservation. An open native
528
- * extension map never qualifies an executor, renderer or runtime by itself.
535
+ * Only the canonical codec is qualified by default. Other owners must provide
536
+ * explicit support; an open extension map or consumer name is insufficient.
529
537
  */
530
- export function assertMissionWorkerHandoff(input, consumer) {
538
+ export function assertMissionWorkerHandoff(input, consumer, workerConsumer) {
531
539
  const seen = new Set();
532
540
  const seenRecords = new Set();
533
541
  function visitExtensions(value) {
@@ -540,8 +548,10 @@ export function assertMissionWorkerHandoff(input, consumer) {
540
548
  if (dispatch.version !== WORKER_CONTRACT_REVISION) {
541
549
  throw new WorkerContractError('OG_CONTRACT_UNSUPPORTED', `OG_CONTRACT_UNSUPPORTED: unsupported worker revision ${String(dispatch.version)}.`);
542
550
  }
543
- const support = canonicalWorkerSupport();
544
- assertWorkerConsumerSupported({ ...support, lossless: consumer === 'canonical', ...(consumer === 'canonical' ? {} : { features: [] }) }, {
551
+ const support = workerConsumer ?? (consumer === 'canonical' ? canonicalWorkerSupport() : {
552
+ revision: WORKER_CONTRACT_REVISION, bundleDigest: workerDefinitionBundleDigest(), features: [], lossless: false,
553
+ });
554
+ assertWorkerConsumerSupported(support, {
545
555
  requiredFeatures: Array.isArray(dispatch.requiredFeatures) ? dispatch.requiredFeatures : [...WORKER_BASELINE_FEATURES],
546
556
  bundleDigest: workerDefinitionBundleDigest(),
547
557
  });
@@ -12,7 +12,7 @@ import { homedir } from 'os';
12
12
  import { existsSync } from 'fs';
13
13
  import { findPackageRoot } from '../cli/find-package-root.js';
14
14
  import { workerConfigDigest, validateWorkerGroupReferences } from '../config/aiwg-config.js';
15
- import { DEFAULT_WORKER_SELECTION, mergeWorkerSelection, WorkerSelectionPolicySchema, validateUserProjectModelConfig } from './provider-policy.js';
15
+ import { DEFAULT_WORKER_SELECTION, WorkerRoleEffortError, mergeWorkerSelection, WorkerSelectionPolicySchema, validateUserProjectModelConfig } from './provider-policy.js';
16
16
  /**
17
17
  * Loads and merges model configurations
18
18
  */
@@ -126,11 +126,16 @@ export class ConfigLoader {
126
126
  const parsed = JSON.parse(content);
127
127
  const validation = validateUserProjectModelConfig(parsed);
128
128
  if (!validation.valid) {
129
+ const missingEffort = validation.diagnostics.find(item => /^\/defaults\/workerSelection\/roleGuidance\/[a-z][a-z0-9-]*\/initialEffort$/.test(item.pointer ?? ''));
130
+ if (missingEffort)
131
+ throw new WorkerRoleEffortError(missingEffort.pointer.split('/')[4]);
129
132
  throw new Error(validation.diagnostics.map(item => `${item.code}: ${item.message}`).join('; '));
130
133
  }
131
134
  return parsed;
132
135
  }
133
136
  catch (error) {
137
+ if (error instanceof WorkerRoleEffortError)
138
+ throw error;
134
139
  if (error.code === 'ENOENT')
135
140
  return null;
136
141
  throw new Error(`Invalid user model config ${this.locations.userConfig}: ${error}`);
@@ -145,11 +150,16 @@ export class ConfigLoader {
145
150
  const parsed = JSON.parse(content);
146
151
  const validation = validateUserProjectModelConfig(parsed);
147
152
  if (!validation.valid) {
153
+ const missingEffort = validation.diagnostics.find(item => /^\/defaults\/workerSelection\/roleGuidance\/[a-z][a-z0-9-]*\/initialEffort$/.test(item.pointer ?? ''));
154
+ if (missingEffort)
155
+ throw new WorkerRoleEffortError(missingEffort.pointer.split('/')[4]);
148
156
  throw new Error(validation.diagnostics.map(item => `${item.code}: ${item.message}`).join('; '));
149
157
  }
150
158
  return parsed;
151
159
  }
152
160
  catch (error) {
161
+ if (error instanceof WorkerRoleEffortError)
162
+ throw error;
153
163
  if (error.code === 'ENOENT')
154
164
  return null;
155
165
  throw new Error(`Invalid project model config ${this.locations.projectConfig}: ${error}`);