@ngockhoale/ukit 3.4.28 → 3.4.30

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 (36) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/manifests/documentation.yaml +88 -0
  3. package/package.json +1 -1
  4. package/src/core/buildPlan.js +11 -1
  5. package/src/core/permissionPolicy.js +1 -1
  6. package/src/core/status.js +3 -1
  7. package/src/index/buildIndex.js +244 -4
  8. package/src/index/impactContext.js +91 -11
  9. package/src/index/languageTools.js +126 -0
  10. package/src/index/paths.js +5 -1
  11. package/src/index/playbookRegistry.js +108 -9
  12. package/src/index/queryIndex.js +415 -1
  13. package/src/index/routeCatalog.js +1 -1
  14. package/src/index/taskRouting.js +25 -3
  15. package/template_project/.claude/commands/ukit/handoff-create.md +5 -0
  16. package/template_project/.claude/commands/ukit/handoff-fullstack.md +43 -4
  17. package/template_project/.claude/hooks/block-dangerous.mjs +7 -1
  18. package/template_project/.claude/hooks/handoff-model-guard.sh +235 -21
  19. package/template_project/.claude/hooks/record-execution.mjs +23 -0
  20. package/template_project/.claude/hooks/skill-router.sh +105 -7
  21. package/template_project/.claude/settings.json +21 -1
  22. package/template_project/.claude/ukit/index/cleanup-task-worktree.mjs +1 -1
  23. package/template_project/.claude/ukit/index/impact-context.mjs +70 -15
  24. package/template_project/.claude/ukit/index/land-worktree.mjs +2 -2
  25. package/template_project/.claude/ukit/index/lib/index-core.mjs +883 -14
  26. package/template_project/.claude/ukit/index/playbook-registry.mjs +108 -9
  27. package/template_project/.claude/ukit/index/query-index.mjs +100 -7
  28. package/template_project/.claude/ukit/index/route-catalog.mjs +1 -1
  29. package/template_project/.claude/ukit/index/route-task.mjs +208 -31
  30. package/template_project/.claude/ukit/runtime/execution-ledger.mjs +455 -24
  31. package/template_project/.claude/ukit/runtime/handoff-intent.mjs +122 -6
  32. package/template_project/.claude/ukit/runtime/resumable-run.mjs +417 -0
  33. package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +635 -28
  34. package/template_project/.omp/hooks/pre/ukit-bridge.js +32 -4
  35. package/template_user/playbooks/feature-implementation.md +9 -0
  36. package/template_user/playbooks/migration.md +4 -0
@@ -167,7 +167,7 @@ const APPROVAL_KEYS = [
167
167
  'schemaVersion', 'taskId', 'receiptSha256', 'verdict', 'executorModel',
168
168
  'reviewerModel', 'reviewedResultTree',
169
169
  ];
170
- const TASK_TOKEN = /^TASK-C97-\d{3}$/;
170
+ const TASK_TOKEN = /^TASK-(?:[A-Z]\d{1,3}-)?\d{3,4}$/;
171
171
  const OID = /^[0-9a-f]{40}$/;
172
172
 
173
173
  function readJson(p, taskId, label) {
@@ -34,30 +34,85 @@ async function main() {
34
34
  return;
35
35
  }
36
36
 
37
- await ensureFreshIndex(rootDir);
38
-
39
- const impact = resolveImpactContext
40
- ? await resolveImpactContext({ rootDir, changedFiles, changedSymbols })
41
- : {
42
- changedFiles,
43
- changedSymbols,
44
- riskLabels: [],
45
- callees: [],
46
- callers: [],
47
- importers: [],
48
- mirrorCounterparts: [],
49
- relatedTests: [],
50
- recommendedTestFiles: [],
51
- };
37
+ let impact = null;
38
+ try {
39
+ await ensureFreshIndex(rootDir);
40
+ impact = resolveImpactContext
41
+ ? await resolveImpactContext({ rootDir, changedFiles, changedSymbols })
42
+ : {
43
+ changedFiles,
44
+ changedSymbols,
45
+ riskLabels: [],
46
+ callees: [],
47
+ callers: [],
48
+ importers: [],
49
+ mirrorCounterparts: [],
50
+ relatedTests: [],
51
+ recommendedTestFiles: [],
52
+ };
53
+ } catch (error) {
54
+ // Index missing, rebuild failed, or lock timed out — answer from source
55
+ // with a bounded search instead of exiting 1.
56
+ const answered = await runFallbackSearch(rootDir, changedFiles, changedSymbols);
57
+ if (!answered) {
58
+ throw error;
59
+ }
60
+ return;
61
+ }
52
62
 
53
63
  printCompact(impact);
54
64
 
65
+ if ((impact.unverifiedImpact ?? []).length > 0) {
66
+ console.log(`unverified-impact: ${impact.unverifiedImpact.slice(0, CALLER_CAP).join(', ')}`);
67
+ console.log('note: name-only matches without import proof — verify before acting');
68
+ }
69
+ const truncated = impact.truncated ?? {};
70
+ const truncatedParts = Object.entries(truncated).filter(([, count]) => count > 0).map(([key, count]) => `${key}:+${count}`);
71
+ if (truncatedParts.length > 0) {
72
+ console.log(`truncated: ${truncatedParts.join(', ')}`);
73
+ }
74
+
55
75
  if (formatImpactConfidenceSummary) {
56
76
  console.log('');
57
77
  console.log(formatImpactConfidenceSummary(impact));
58
78
  }
59
79
  }
60
80
 
81
+ // Bounded source search over the changed file basenames and symbol names.
82
+ // Returns true when it produced output; false when the fallback itself is
83
+ // unavailable so the original index error can surface.
84
+ async function runFallbackSearch(rootDir, changedFiles, changedSymbols) {
85
+ if (typeof indexCore.boundedSourceSearch !== 'function') {
86
+ return false;
87
+ }
88
+ const queries = [
89
+ ...changedFiles.map((filePath) => String(filePath).split('/').pop().replace(/\.[^.]+$/, '')),
90
+ ...changedSymbols,
91
+ ].filter(Boolean).slice(0, 4);
92
+ try {
93
+ console.log('source: fallback-search');
94
+ let printed = 0;
95
+ for (const searchQuery of queries) {
96
+ const rows = await indexCore.boundedSourceSearch({ rootDir, query: searchQuery, limit: 5 });
97
+ console.log(`[ukit:impact:fallback] ${rows.length} source matches for: ${searchQuery}`);
98
+ for (const row of rows) {
99
+ console.log(` ${row.filePath}${row.line ? `:${row.line}` : ''}`);
100
+ }
101
+ if (rows.truncated?.length) {
102
+ console.log(`[ukit:impact:fallback] search truncated (${rows.truncated.join(',')}) — NOT exhaustive`);
103
+ }
104
+ printed += rows.length;
105
+ }
106
+ if (printed === 0) {
107
+ console.log('[ukit:impact:fallback] no source matches — verify changed files manually');
108
+ }
109
+ return true;
110
+ } catch (fallbackError) {
111
+ console.error(`fallback-search failed: ${fallbackError && fallbackError.message ? fallbackError.message : fallbackError}`);
112
+ return false;
113
+ }
114
+ }
115
+
61
116
  async function ensureFreshIndex(rootDir) {
62
117
  if (typeof indexCore.ensureIndexFresh === 'function') {
63
118
  return indexCore.ensureIndexFresh({ rootDir });
@@ -168,8 +168,8 @@ function readScope() {
168
168
  }
169
169
  if (parsed.schemaVersion !== 1) refuse(parsed.taskId, 'invalid-scope', 'schemaVersion must be 1');
170
170
  const { taskId, baseCommit, sourceRoot, destinationRoot, sourceHead, destinationHead, targetPaths } = parsed;
171
- if (typeof taskId !== 'string' || !/^TASK-C97-\d{3}$/.test(taskId)) {
172
- refuse(taskId, 'invalid-scope', 'taskId must be an exact TASK-C97-009-style token');
171
+ if (typeof taskId !== 'string' || !/^TASK-(?:[A-Z]\d{1,3}-)?\d{3,4}$/.test(taskId)) {
172
+ refuse(taskId, 'invalid-scope', 'taskId must be an exact TASK-001 or TASK-C97-009 style token');
173
173
  }
174
174
  for (const [k, v] of Object.entries({ baseCommit, sourceRoot, destinationRoot, sourceHead, destinationHead })) {
175
175
  if (typeof v !== 'string' || v.length === 0) refuse(taskId, 'invalid-scope', `${k} must be a nonempty string`);