@ngockhoale/ukit 3.4.29 → 3.4.31

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 (49) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/manifests/documentation.yaml +100 -0
  3. package/package.json +1 -1
  4. package/src/core/executionContracts.js +32 -0
  5. package/src/core/permissionPolicy.js +1 -1
  6. package/src/decision/client.js +15 -0
  7. package/src/decision/protocol.js +12 -14
  8. package/src/index/buildIndex.js +244 -4
  9. package/src/index/impactContext.js +91 -11
  10. package/src/index/languageTools.js +126 -0
  11. package/src/index/paths.js +5 -1
  12. package/src/index/playbookRegistry.js +108 -9
  13. package/src/index/queryIndex.js +415 -1
  14. package/src/index/routeCatalog.js +1 -1
  15. package/src/index/routeResolver.js +2 -2
  16. package/src/index/taskRouting.js +28 -4
  17. package/template_project/.claude/commands/ukit/handoff-create.md +5 -0
  18. package/template_project/.claude/commands/ukit/handoff-fullstack.md +43 -4
  19. package/template_project/.claude/hooks/block-dangerous.mjs +7 -1
  20. package/template_project/.claude/hooks/handoff-model-guard.sh +235 -21
  21. package/template_project/.claude/hooks/record-execution.mjs +21 -0
  22. package/template_project/.claude/hooks/skill-router.sh +187 -16
  23. package/template_project/.claude/settings.json +43 -1
  24. package/template_project/.claude/ukit/index/cleanup-task-worktree.mjs +1 -1
  25. package/template_project/.claude/ukit/index/fix-loop-escalation.mjs +10 -4
  26. package/template_project/.claude/ukit/index/impact-context.mjs +70 -15
  27. package/template_project/.claude/ukit/index/land-worktree.mjs +2 -2
  28. package/template_project/.claude/ukit/index/lib/index-core.mjs +883 -14
  29. package/template_project/.claude/ukit/index/playbook-registry.mjs +108 -9
  30. package/template_project/.claude/ukit/index/query-index.mjs +100 -7
  31. package/template_project/.claude/ukit/index/review-verdict.mjs +14 -4
  32. package/template_project/.claude/ukit/index/route-catalog.mjs +1 -1
  33. package/template_project/.claude/ukit/index/route-resolver.mjs +34 -2
  34. package/template_project/.claude/ukit/index/route-task.mjs +243 -38
  35. package/template_project/.claude/ukit/index/sidecar-decision.mjs +3 -59
  36. package/template_project/.claude/ukit/index/unic-decision.mjs +27 -14
  37. package/template_project/.claude/ukit/runtime/cron-operation-adapter.mjs +43 -0
  38. package/template_project/.claude/ukit/runtime/execution-ledger.mjs +612 -56
  39. package/template_project/.claude/ukit/runtime/handoff-intent.mjs +122 -6
  40. package/template_project/.claude/ukit/runtime/loop-state.mjs +1 -1
  41. package/template_project/.claude/ukit/runtime/operation-evidence.mjs +87 -0
  42. package/template_project/.claude/ukit/runtime/resumable-run.mjs +417 -0
  43. package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +635 -28
  44. package/template_project/.omp/hooks/pre/ukit-bridge.js +40 -5
  45. package/template_project/AGENTS.md +1 -1
  46. package/template_project/CLAUDE.md +1 -1
  47. package/template_project/instructions/core.md +1 -1
  48. package/template_user/playbooks/feature-implementation.md +9 -0
  49. package/template_user/playbooks/migration.md +4 -0
@@ -83,23 +83,20 @@ export function encodeQuestion(question) {
83
83
  description: 'true iff the proposition holds; interpreted as P(true).',
84
84
  };
85
85
  } else {
86
- // score — ordinal integer; the legend MUST be defined in the description.
87
- const legend = Array.isArray(candidates) ? candidates : [];
88
- value = { type: 'integer' };
89
- if (legend.length > 0) {
90
- value.minimum = 1;
91
- value.maximum = legend.length;
92
- value.description = `Ordered scale: ${legend
93
- .map((label, i) => `${i + 1}=${label}`)
94
- .join(', ')}.`;
86
+ if (!Array.isArray(candidates) || candidates.length === 0
87
+ || candidates.some(label => typeof label !== 'string' || !label.trim())
88
+ || new Set(candidates).size !== candidates.length) {
89
+ throw new TypeError('score requires unique non-empty ordinal labels.');
95
90
  }
91
+ value = { type: 'string', enum: [...candidates] };
96
92
  }
97
93
 
98
94
  return {
99
95
  type: 'function',
100
96
  function: {
101
97
  name: decisionKey,
102
- description: String(instruction ?? ''),
98
+ description: String(instruction ?? '') + (kind === 'score'
99
+ ? `\nOrdered ordinal rubric: ${candidates.map((label, i) => `${i + 1}=${JSON.stringify(label)}`).join(', ')}. Choose exactly one label; do not return an expected numeric score.` : ''),
103
100
  parameters: {
104
101
  type: 'object',
105
102
  properties: { value },
@@ -196,11 +193,12 @@ function validateValue(question, args) {
196
193
  if (isFiniteNumber(value) && value >= 0 && value <= 1) return { value };
197
194
  return 'missing-value';
198
195
  }
199
- // score — finite integer within the declared legend range.
200
- if (!isFiniteNumber(value) || !Number.isInteger(value)) return 'missing-value';
196
+ // Decode an explicit choice losslessly; never reinterpret native expectations.
201
197
  const legend = Array.isArray(question.candidates) ? question.candidates : [];
202
- if (legend.length > 0 && (value < 1 || value > legend.length)) return 'out-of-enum';
203
- return { value };
198
+ if (!legend.length || new Set(legend).size !== legend.length) return 'out-of-enum';
199
+ if (typeof value !== 'string') return 'missing-value';
200
+ const ordinal = legend.indexOf(value);
201
+ return ordinal < 0 ? 'out-of-enum' : { value: ordinal + 1 };
204
202
  }
205
203
 
206
204
  export function parseBatchResponse(response, questions) {
@@ -834,6 +832,21 @@ export async function requestBatch(batch, {
834
832
  return outcome('invalid', { fallbackCode: 'empty-batch' });
835
833
  }
836
834
 
835
+ const maxBatch = dp.maxBatchQuestions ?? 8;
836
+ const maxEnum = dp.maxEnumLabels ?? 11;
837
+ if (![maxBatch, maxEnum].every(n => Number.isInteger(n) && n > 0)) {
838
+ return outcome('unsupported', { fallbackCode: 'invalid-decision-limits' });
839
+ }
840
+ if (questions.length > maxBatch) {
841
+ return outcome('unsupported', { fallbackCode: 'max-batch-questions' });
842
+ }
843
+ if (questions.some(q => Array.isArray(q.candidates) && q.candidates.length > maxEnum)) {
844
+ return outcome('unsupported', { fallbackCode: 'max-enum-labels' });
845
+ }
846
+ if (new Set(questions.map(q => q.decisionKey)).size !== questions.length) {
847
+ return outcome('invalid', { fallbackCode: 'duplicate-question' });
848
+ }
849
+
837
850
  // Language classification covers state + question text; the English
838
851
  // checkpoint is reachable only when the whole request proves English.
839
852
  // Malformed batch input (cyclic state, non-serializable values, internal
@@ -0,0 +1,43 @@
1
+ // Capability adapters normalize native results; completion consumes their common receipt shape.
2
+ const ID = /^[a-f0-9-]{8,36}$/i;
3
+ function responseText(response) {
4
+ if (typeof response === 'string') return response.trim();
5
+ if (response?.content !== undefined && (!Array.isArray(response.content)
6
+ || !response.content.every((c) => c?.type === 'text' && typeof c.text === 'string'))) return '';
7
+ if (typeof response?.output === 'string') return response.output.trim();
8
+ if (typeof response?.stdout === 'string') return response.stdout.trim();
9
+ if (Array.isArray(response?.content) && response.content.every((c) => c?.type === 'text' && typeof c.text === 'string')) {
10
+ return response.content.map((c) => c.text).join('\n').trim();
11
+ }
12
+ return '';
13
+ }
14
+ const capabilities = {
15
+ CronDelete: { domain: 'scheduled-job', role: 'mutation', normalize(input, response) {
16
+ const target = typeof input?.id === 'string' && ID.test(input.id) ? input.id.toLowerCase() : null;
17
+ return { target, accepted: Boolean(target && responseText(response) === `Cancelled ${input.id}`) };
18
+ } },
19
+ CronList: { domain: 'scheduled-job', role: 'state', normalize(_input, response) {
20
+ const body = response?.details && Object.hasOwn(response.details, 'jobs') ? response.details : response;
21
+ const structured = body != null && typeof body === 'object' && Object.hasOwn(body, 'jobs');
22
+ const jobs = Array.isArray(body?.jobs) ? body.jobs : null;
23
+ const ids = jobs && jobs.length <= 256 && jobs.every((job) => typeof job?.id === 'string' && ID.test(job.id))
24
+ ? jobs.map((job) => job.id.toLowerCase()) : null;
25
+ const partial = [body, response].some((value) => value?.partial === true || value?.hasMore === true
26
+ || Boolean(value?.nextCursor) || value?.complete === false);
27
+ const supportedContent = response?.content === undefined || (Array.isArray(response.content)
28
+ && response.content.every((c) => c?.type === 'text' && typeof c.text === 'string'));
29
+ const valid = !partial && supportedContent
30
+ && (structured ? ids !== null : /^No scheduled jobs\.?$/.test(responseText(response)));
31
+ return { target: null, state: 'absent', presentTargets: valid ? ids || [] : null, complete: valid, accepted: valid };
32
+ } },
33
+ };
34
+ export function cronOperationReceipt(toolName, input, response, transportSuccess, { sessionId, harness } = {}) {
35
+ const capability = capabilities[toolName];
36
+ if (!capability) return null;
37
+ const normalized = capability.normalize(input, response);
38
+ const { accepted, ...state } = normalized;
39
+ const scope = typeof sessionId === 'string' && sessionId.trim() ? `session:${sessionId.trim()}` : null;
40
+ return { kind: 'operation', success: transportSuccess === true && accepted && Boolean(scope),
41
+ transportSuccess: transportSuccess === true, harness,
42
+ operation: { domain: capability.domain, role: capability.role, scope, ...state } };
43
+ }