@ikon85/agent-workflow-kit 0.44.1 → 0.45.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 (67) hide show
  1. package/.agents/skills/audit-skills/SKILL.md +7 -4
  2. package/.agents/skills/code-review/SKILL.md +7 -4
  3. package/.agents/skills/codebase-design/DESIGN-IT-TWICE.md +7 -4
  4. package/.agents/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +7 -4
  5. package/.agents/skills/improve-codebase-architecture/SKILL.md +7 -4
  6. package/.agents/skills/orchestrate-wave/SKILL.md +1 -1
  7. package/.agents/skills/orchestrate-wave/references/dispatch-subagents.md +9 -0
  8. package/.agents/skills/orchestrate-wave/references/dispatch-workflow.md +9 -0
  9. package/.agents/skills/research/SKILL.md +7 -4
  10. package/.agents/skills/to-issues/SKILL.md +25 -4
  11. package/.claude/skills/audit-skills/SKILL.md +7 -4
  12. package/.claude/skills/code-review/SKILL.md +7 -4
  13. package/.claude/skills/codebase-design/DESIGN-IT-TWICE.md +7 -4
  14. package/.claude/skills/codex-build/SKILL.md +13 -0
  15. package/.claude/skills/codex-review/SKILL.md +13 -0
  16. package/.claude/skills/grill-me-codex/SKILL.md +14 -0
  17. package/.claude/skills/grill-with-docs-codex/SKILL.md +14 -0
  18. package/.claude/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +7 -4
  19. package/.claude/skills/improve-codebase-architecture/SKILL.md +7 -4
  20. package/.claude/skills/orchestrate-wave/SKILL.md +1 -1
  21. package/.claude/skills/orchestrate-wave/references/dispatch-subagents.md +9 -0
  22. package/.claude/skills/orchestrate-wave/references/dispatch-workflow.md +9 -0
  23. package/.claude/skills/research/SKILL.md +7 -4
  24. package/.claude/skills/skill-manifest.json +34 -23
  25. package/.claude/skills/to-issues/SKILL.md +25 -4
  26. package/README.md +71 -0
  27. package/agent-workflow-kit.package.json +149 -45
  28. package/package.json +1 -1
  29. package/scripts/doctrine-migration/index.mjs +296 -0
  30. package/scripts/kit-release.mjs +41 -9
  31. package/src/cli.mjs +521 -80
  32. package/src/commands/routing-status.mjs +288 -0
  33. package/src/commands/update.mjs +9 -1
  34. package/src/consumer-migrations.json +23 -1
  35. package/src/lib/bundle.mjs +158 -2
  36. package/src/lib/consumerMigrations.mjs +55 -0
  37. package/src/lib/dispatchJournal.mjs +300 -0
  38. package/src/lib/dispatchPlan.mjs +286 -0
  39. package/src/lib/dispatchReceipt.mjs +226 -89
  40. package/src/lib/frontendWorkloads.mjs +35 -33
  41. package/src/lib/routeDispatcher.mjs +367 -70
  42. package/src/lib/routingAccessGraph.mjs +265 -20
  43. package/src/lib/routingAccessGraphStore.mjs +300 -0
  44. package/src/lib/routingAdapters/claude.mjs +104 -4
  45. package/src/lib/routingAdapters/codex.mjs +132 -7
  46. package/src/lib/routingAdapters/hostBridge.mjs +291 -0
  47. package/src/lib/routingCatalog.mjs +201 -24
  48. package/src/lib/routingDispatchLease.mjs +253 -0
  49. package/src/lib/routingEvidenceCache.mjs +78 -0
  50. package/src/lib/routingIntent.mjs +176 -10
  51. package/src/lib/routingIntentClassifier.mjs +137 -0
  52. package/src/lib/routingInventory/snapshots/claude.json +49 -0
  53. package/src/lib/routingInventory/snapshots/codex.json +109 -0
  54. package/src/lib/routingInventory.mjs +182 -0
  55. package/src/lib/routingPolicy.mjs +193 -6
  56. package/src/lib/routingProfile.mjs +1251 -123
  57. package/src/lib/routingProfilePolicy.mjs +156 -0
  58. package/src/lib/routingProfileStorage.mjs +299 -0
  59. package/src/lib/routingResolver.mjs +369 -86
  60. package/src/lib/routingSources/artificialAnalysis.mjs +19 -7
  61. package/src/lib/routingSources/benchlm.mjs +5 -0
  62. package/src/lib/routingSources/codeArena.mjs +13 -3
  63. package/src/lib/routingSources/deepswe.mjs +19 -5
  64. package/src/lib/routingSources/openhands.mjs +17 -4
  65. package/src/lib/routingSources/openhandsFrontend.mjs +13 -3
  66. package/src/lib/safeText.mjs +26 -0
  67. package/src/lib/updateCandidate.mjs +36 -3
@@ -1,7 +1,15 @@
1
+ import {
2
+ assertPublishedEffort,
3
+ evidenceFreshness,
4
+ evidenceIdentity,
5
+ evidenceSourceClaim,
6
+ } from '../routingCatalog.mjs';
7
+
1
8
  const SOURCE_ID = 'deepswe';
2
9
  const OWNER = 'DataCurve';
3
10
  const ARTIFACT_URL =
4
11
  'https://deepswe.datacurve.ai/artifacts/v1.1/leaderboard-live.json';
12
+ const WORKLOAD = evidenceIdentity({ workload: 'repository-repair', axis: 'functional' });
5
13
 
6
14
  function object(value, field) {
7
15
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
@@ -34,7 +42,9 @@ function context({ snapshotHash, observedAt, expiresAt }) {
34
42
  }
35
43
 
36
44
  function ingest({ payload, ...inputContext }) {
37
- const { snapshotHash, observedAt, expiresAt } = context(inputContext);
45
+ // The caller's window bounds the refresh; the observation is dated by the
46
+ // Kit-side freshness policy for this source, never by the owner artifact.
47
+ const { snapshotHash, observedAt } = context(inputContext);
38
48
  object(payload, 'DeepSWE payload');
39
49
  const artifact = object(payload.artifact, 'DeepSWE artifact');
40
50
  if (artifact.owner !== OWNER || artifact.url !== ARTIFACT_URL) {
@@ -54,7 +64,10 @@ function ingest({ payload, ...inputContext }) {
54
64
  object(row, `DeepSWE rows[${index}]`);
55
65
  const providerId = string(row.provider, `DeepSWE rows[${index}].provider`);
56
66
  const modelId = string(row.model, `DeepSWE rows[${index}].model`);
57
- const effort = string(row.effort, `DeepSWE rows[${index}].effort`);
67
+ const effort = assertPublishedEffort({
68
+ sourceId: SOURCE_ID,
69
+ effort: string(row.effort, `DeepSWE rows[${index}].effort`),
70
+ });
58
71
  const id = `${SOURCE_ID}:${benchmarkVersion}:${providerId}:${modelId}:${effort}`;
59
72
  if (observationIds.has(id)) throw new TypeError(`duplicate DeepSWE observation: ${id}`);
60
73
  observationIds.add(id);
@@ -64,7 +77,7 @@ function ingest({ payload, ...inputContext }) {
64
77
  providerId,
65
78
  modelId,
66
79
  effort,
67
- workload: 'development',
80
+ workload: WORKLOAD,
68
81
  harness: { id: harnessId, version: harnessVersion },
69
82
  score: number(row.score, `DeepSWE rows[${index}].score`),
70
83
  source: {
@@ -82,11 +95,11 @@ function ingest({ payload, ...inputContext }) {
82
95
  `DeepSWE rows[${index}].confidence95HalfWidth`,
83
96
  ),
84
97
  },
85
- freshness: { observedAt, expiresAt },
98
+ freshness: evidenceFreshness({ sourceId: SOURCE_ID, observedAt }),
86
99
  cost: {
87
100
  amount: number(row.averageCostUsd, `DeepSWE rows[${index}].averageCostUsd`),
88
101
  currency: 'USD',
89
- unit: 'task',
102
+ unit: 'attempt',
90
103
  },
91
104
  });
92
105
  });
@@ -102,5 +115,6 @@ export const deepSweSource = Object.freeze({
102
115
  sourceId: SOURCE_ID,
103
116
  owner: OWNER,
104
117
  artifactUrl: ARTIFACT_URL,
118
+ claim: evidenceSourceClaim(SOURCE_ID),
105
119
  ingest,
106
120
  });
@@ -1,6 +1,15 @@
1
+ import {
2
+ assertCostUnit,
3
+ assertPublishedEffort,
4
+ evidenceFreshness,
5
+ evidenceIdentity,
6
+ evidenceSourceClaim,
7
+ } from '../routingCatalog.mjs';
8
+
1
9
  const SOURCE_ID = 'openhands-evaluation';
2
10
  const OWNER = 'All Hands AI';
3
11
  const ARTIFACT_URL = 'https://github.com/All-Hands-AI/OpenHands/tree/main/evaluation';
12
+ const WORKLOAD = evidenceIdentity({ workload: 'repository-repair', axis: 'functional' });
4
13
 
5
14
  function object(value, field) {
6
15
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
@@ -50,7 +59,10 @@ function ingest({ payload, snapshotHash, observedAt, expiresAt }) {
50
59
  object(row, `OpenHands results[${index}]`);
51
60
  const providerId = string(row.provider, `OpenHands results[${index}].provider`);
52
61
  const modelId = string(row.model, `OpenHands results[${index}].model`);
53
- const effort = string(row.effort, `OpenHands results[${index}].effort`);
62
+ const effort = assertPublishedEffort({
63
+ sourceId: SOURCE_ID,
64
+ effort: string(row.effort, `OpenHands results[${index}].effort`),
65
+ });
54
66
  const id = `${SOURCE_ID}:${benchmarkVersion}:${providerId}:${modelId}:${effort}`;
55
67
  if (observationIds.has(id)) throw new TypeError(`duplicate OpenHands observation: ${id}`);
56
68
  observationIds.add(id);
@@ -61,7 +73,7 @@ function ingest({ payload, snapshotHash, observedAt, expiresAt }) {
61
73
  providerId,
62
74
  modelId,
63
75
  effort,
64
- workload: 'development',
76
+ workload: WORKLOAD,
65
77
  harness: { id: harnessId, version: harnessVersion },
66
78
  score: number(row.resolvedRate, `OpenHands results[${index}].resolvedRate`),
67
79
  source: {
@@ -76,14 +88,14 @@ function ingest({ payload, snapshotHash, observedAt, expiresAt }) {
76
88
  kind: 'standard-error',
77
89
  value: number(row.standardError, `OpenHands results[${index}].standardError`),
78
90
  },
79
- freshness: { observedAt, expiresAt },
91
+ freshness: evidenceFreshness({ sourceId: SOURCE_ID, observedAt }),
80
92
  cost: {
81
93
  amount: number(meanCost.amount, `OpenHands results[${index}].meanCost.amount`),
82
94
  currency: string(
83
95
  meanCost.currency,
84
96
  `OpenHands results[${index}].meanCost.currency`,
85
97
  ),
86
- unit: string(meanCost.unit, `OpenHands results[${index}].meanCost.unit`),
98
+ unit: assertCostUnit(meanCost.unit, `OpenHands results[${index}].meanCost.unit`),
87
99
  },
88
100
  });
89
101
  });
@@ -98,5 +110,6 @@ export const openHandsSource = Object.freeze({
98
110
  sourceId: SOURCE_ID,
99
111
  owner: OWNER,
100
112
  artifactUrl: ARTIFACT_URL,
113
+ claim: evidenceSourceClaim(SOURCE_ID),
101
114
  ingest,
102
115
  });
@@ -1,4 +1,10 @@
1
1
  import { frontendEvidenceWorkload } from '../frontendWorkloads.mjs';
2
+ import {
3
+ assertCostUnit,
4
+ assertPublishedEffort,
5
+ evidenceFreshness,
6
+ evidenceSourceClaim,
7
+ } from '../routingCatalog.mjs';
2
8
 
3
9
  const OWNER_URL = 'https://www.openhands.dev/blog/openhands-index';
4
10
  const SOURCE_ID = 'openhands-frontend';
@@ -70,7 +76,10 @@ function ingest({ payload, snapshotHash, observedAt, expiresAt }) {
70
76
  object(row, `OpenHands rows[${index}]`);
71
77
  const providerId = string(row.providerId, `OpenHands rows[${index}].providerId`);
72
78
  const modelId = string(row.modelId, `OpenHands rows[${index}].modelId`);
73
- const effort = string(row.effort, `OpenHands rows[${index}].effort`);
79
+ const effort = assertPublishedEffort({
80
+ sourceId: SOURCE_ID,
81
+ effort: string(row.effort, `OpenHands rows[${index}].effort`),
82
+ });
74
83
  const modelIdentity = `${providerId}:${modelId}`;
75
84
  if (!modelIds.has(modelIdentity)) {
76
85
  models.push({ providerId, modelId });
@@ -116,11 +125,11 @@ function ingest({ payload, snapshotHash, observedAt, expiresAt }) {
116
125
  status: row.status,
117
126
  sampleSize: finite(row.sampleSize, `OpenHands rows[${index}].sampleSize`),
118
127
  },
119
- freshness: { observedAt, expiresAt },
128
+ freshness: evidenceFreshness({ sourceId: SOURCE_ID, observedAt }),
120
129
  cost: {
121
130
  amount: finite(row.cost.amount, `OpenHands rows[${index}].cost.amount`),
122
131
  currency: string(row.cost.currency, `OpenHands rows[${index}].cost.currency`),
123
- unit: string(row.cost.unit, `OpenHands rows[${index}].cost.unit`),
132
+ unit: assertCostUnit(row.cost.unit, `OpenHands rows[${index}].cost.unit`),
124
133
  },
125
134
  });
126
135
  }
@@ -131,5 +140,6 @@ export const openHandsFrontendSource = Object.freeze({
131
140
  sourceId: SOURCE_ID,
132
141
  owner: 'OpenHands',
133
142
  artifactUrl: OWNER_URL,
143
+ claim: evidenceSourceClaim(SOURCE_ID),
134
144
  ingest,
135
145
  });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Text that reaches a terminal verbatim.
3
+ *
4
+ * Two shipped surfaces need the same guarantee and must not each grow their own
5
+ * copy: the update plan renders Kit-authored manifest prose, and `routing status`
6
+ * renders diagnostics that can carry an external error message. Either would
7
+ * repaint the terminal if a control sequence rode through, so both strip C0/C1
8
+ * control characters here.
9
+ *
10
+ * This module is a leaf on purpose: it imports nothing, so an installed consumer
11
+ * receives it whole without dragging the installer-side update machinery along.
12
+ */
13
+
14
+ /**
15
+ * Validate that a value is a string and strip C0/C1 control characters plus
16
+ * redundant whitespace from it. Returns null for a non-string or an input that
17
+ * sanitizes to nothing, so a caller can distinguish "absent" from "empty".
18
+ */
19
+ export function sanitizeReadinessText(value) {
20
+ if (typeof value !== 'string') return null;
21
+ const cleaned = value
22
+ .replace(/[\u0000-\u001f\u007f-\u009f]+/g, ' ')
23
+ .replace(/\s+/g, ' ')
24
+ .trim();
25
+ return cleaned || null;
26
+ }
@@ -11,6 +11,7 @@ import { validateConsumerFile } from './consumerPath.mjs';
11
11
  import { sha256File } from './hash.mjs';
12
12
  import { stubSentinel } from './sentinel.mjs';
13
13
  import { STUB_TARGETS } from './bundle.mjs';
14
+ import { sanitizeReadinessText } from './safeText.mjs';
14
15
  import {
15
16
  CONSUMER_MANIFEST_NAME, CONSUMER_ORIGIN, READINESS_MANIFEST_PATH,
16
17
  filesForInstallRole, indexByPath, readManifest, validateManifest, writeManifest,
@@ -415,7 +416,7 @@ export async function adoptReadinessCandidate({
415
416
  migrations,
416
417
  migrated: migrations.map(({ path }) => path),
417
418
  migrationConflicts,
418
- availability: readinessDiff(before, after),
419
+ availability: readinessDiff(before, after, nextManifest?.readiness?.capabilities),
419
420
  incompatible,
420
421
  };
421
422
  }
@@ -568,11 +569,38 @@ async function readinessSnapshot(root, manifest) {
568
569
  return { skills, capabilities };
569
570
  }
570
571
 
571
- function readinessDiff(before, after) {
572
+ /**
573
+ * Manifest prose is Kit-authored but reaches a terminal verbatim, so every
574
+ * catalog string is validated (must be a string) and stripped of C0/C1 control
575
+ * characters — an escape sequence would otherwise repaint the update plan.
576
+ *
577
+ * The implementation moved to `./safeText.mjs` when `routing status` needed the
578
+ * same guarantee: that command ships to consumers, this module does not, so
579
+ * importing it from here would have escaped the installed consumer. Imported
580
+ * and re-exported rather than relocated, so this module keeps using it and
581
+ * every existing caller keeps its import path.
582
+ */
583
+ export { sanitizeReadinessText };
584
+
585
+ /** One unresolved capability in plain terms: what it is and what closes it. */
586
+ function unresolvedEntry(capability, state, catalogEntry) {
587
+ return {
588
+ capability,
589
+ state,
590
+ title: sanitizeReadinessText(catalogEntry?.title),
591
+ remedy: sanitizeReadinessText(catalogEntry?.remedy),
592
+ evidencePaths: (catalogEntry?.evidence?.paths ?? [])
593
+ .map(sanitizeReadinessText)
594
+ .filter((path) => path !== null),
595
+ };
596
+ }
597
+
598
+ function readinessDiff(before, after, catalog = {}) {
572
599
  const newlyAvailable = [];
573
600
  const newlyDegraded = [];
574
601
  const newlyBlocked = [];
575
602
  const unresolved = new Set();
603
+ const unresolvedEntries = [];
576
604
  for (const [skill, current] of Object.entries(after.skills)) {
577
605
  const prior = before.skills[skill];
578
606
  if (current.verdict === 'blocked' && prior?.verdict !== 'blocked') newlyBlocked.push(skill);
@@ -582,11 +610,16 @@ function readinessDiff(before, after) {
582
610
  }
583
611
  }
584
612
  for (const [capability, result] of Object.entries(after.capabilities)) {
585
- if (result.state !== 'ready') unresolved.add(`${capability}:${result.state}`);
613
+ if (result.state === 'ready') continue;
614
+ unresolved.add(`${capability}:${result.state}`);
615
+ unresolvedEntries.push(unresolvedEntry(capability, result.state, catalog?.[capability]));
586
616
  }
587
617
  return {
588
618
  newlyAvailable: newlyAvailable.sort(), newlyDegraded: newlyDegraded.sort(),
589
619
  newlyBlocked: newlyBlocked.sort(), stillUnresolved: [...unresolved].sort(),
620
+ unresolved: unresolvedEntries.sort(
621
+ (a, b) => (a.capability < b.capability ? -1 : 1),
622
+ ),
590
623
  };
591
624
  }
592
625