@sabaiway/agent-workflow-kit 11.1.0 → 12.0.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.
@@ -22,6 +22,8 @@ import { homedir } from 'node:os';
22
22
  import { join, dirname } from 'node:path';
23
23
  import { fileURLToPath } from 'node:url';
24
24
  import { detectBackends, wrapperCmdFor, wrapperContractFor } from './detect-backends.mjs';
25
+ import { surveyVehicle as surveyReviewVehicle } from './cheap-agents-read.mjs';
26
+ import { isReadyMember, skippedLine } from './review-roster-resolve.mjs';
25
27
  // The host-level bridge-settings registry (manifest-as-source) + its allowed-value labels. READ-ONLY
26
28
  // core only — never the writer — so this read-only advisor never imports the atomic-write core.
27
29
  import { loadRegistry, allowedLabel } from './bridge-settings-read.mjs';
@@ -150,7 +152,7 @@ export const extractSection = (text, activity) => {
150
152
 
151
153
  // ── resolution + rendering ─────────────────────────────────────────────────────────
152
154
 
153
- const resolveAllSlots = ({ activity, config, detection, overrides }) => {
155
+ const resolveAllSlots = ({ activity, config, detection, overrides, surveyLens }) => {
154
156
  // The host-level settings knobs (manifest-as-source), best-effort: a corrupt bundle degrades to none
155
157
  // and the advisor never crashes. Attached per wrapper cmd via each knob's `appliesTo`. Fact-only —
156
158
  // model/effort are not knobs, so no model claim can ride here.
@@ -163,7 +165,7 @@ const resolveAllSlots = ({ activity, config, detection, overrides }) => {
163
165
  })();
164
166
  const knobsFor = (cmd) => [...registry.values()].filter((k) => (k.appliesTo ?? []).includes(cmd));
165
167
  return Object.entries(ACTIVITIES[activity].slots).map(([slot, slotType]) => {
166
- const resolved = resolveActivityRecipe({ config: config ?? {}, readiness: detection, activity, slot, override: overrides[slot] });
168
+ const resolved = resolveActivityRecipe({ config: config ?? {}, readiness: detection, activity, slot, override: overrides[slot], surveyLens });
167
169
  if (isSwitchSlot(slotType)) return { slot, slotType, ...resolved, backends: [], contracts: [], vehicles: [] };
168
170
  // The concrete wrapper set this slot's EFFECTIVE recipe dispatches (empty for solo). Reuse
169
171
  // planRecipe's drift-guarded dispatch for WHICH backends, then resolve each (backend, role) to its
@@ -177,7 +179,7 @@ const resolveAllSlots = ({ activity, config, detection, overrides }) => {
177
179
  // pairs, BEFORE they are flattened to wrapper names (the name array cannot reconstruct the role).
178
180
  // Every slot with a non-empty dispatch gets contracts — including execute=delegated; the contract
179
181
  // is NEVER gated by REVIEW_RECIPES (that set gates only the review-loop economics block).
180
- const contracts = bridged
182
+ const contracts = activity === 'feedback-triage' ? [] : bridged
181
183
  .map((d) => ({ backend: d.backend, role: d.role, cmd: wrapperCmdFor(d.backend, d.role), contract: wrapperContractFor(d.backend, d.role) }))
182
184
  .filter((c) => c.cmd && c.contract)
183
185
  // `retired` rides along: without it this surface advertised a RETIRED key as an ordinary
@@ -230,7 +232,8 @@ const backendSetLabel = (backends) =>
230
232
  // review slot can resolve or request reviewed|council (execute floors at solo|delegated), so the gate
231
233
  // reads the recipe, the requested recipe and the roster.
232
234
  const REVIEW_RECIPES = new Set(['reviewed', 'council']);
233
- const CONSULT_LINE = ' • Before every fold of a finding raised by a review member (a bridge backend or a placed lens): ASK that member whether the proposed fold solves it and adds no new problem; WAIT for its answer, READ it, then edit only as accepted or corrected — agy: agy-review --continue --decided @f --focus "Finding: <finding>. Proposed fold: <exact fold>. Does this proposed fold solve the finding and add no new problem? Reply accept, or correct with exact replacement text."; codex: fresh codex-review plan <consult-brief> written before the working tree changes; a placed lens: re-dispatch the same lens vehicle with the finding and the proposed fold. A self-review finding, or any finding when no review member ran, is folded directly — the exemption is the finding\'s ORIGIN, never the recipe word on the slot line.';
235
+ const CONSULT_LINE = ' • Before every fold of a finding raised by a review member (a bridge backend or a placed lens): ASK that member whether the proposed fold solves it and adds no new problem; WAIT for its answer, READ it, then edit only as accepted or corrected — agy: agy-review --continue --decided @f --focus "Finding: <finding>. Proposed fold: <exact fold>. Does this proposed fold solve the finding and add no new problem? If this finding is the second case against the same check, reply with the replacement invariant, not an added case. ' +
236
+ 'Reply accept, or correct with exact replacement text."; codex: fresh codex-review plan <consult-brief> with the same payload written before the working tree changes; a placed lens: re-dispatch the same lens vehicle with the same payload. A self-review finding, or any finding when no review member ran, is folded directly — the exemption is the finding\'s ORIGIN, never the recipe word on the slot line.';
234
237
  const ARMED_CONSULT_LINE = ' • ARMED pre-fold sequence for a bridge-raised finding: the round is open → dispatch that bridge\'s consult with a nonce → WAIT and READ → fold accepted or corrected → flow-writer consult-attestation <planId> --backend <id> --nonce <n> --proposed-fix-digest <the-sha256-of-the-fold-text> → then edit. A lens-raised finding re-dispatches the lens without a nonce, WAIT and READ, then edit as accepted or corrected — it mints no manifest and no attestation — only its per-round participation rides its internal-attestation.';
235
238
  // activity-aware (AD-046): the triage classification vocabulary rides EVERY review-backed activity;
236
239
  // the LEDGER pointer renders ONLY for plan-execution — the ledger is plan-execution-scoped (AD-045),
@@ -271,8 +274,40 @@ const reviewLoopAdvice = (slots, activity, flowArmed = false, plans = []) =>
271
274
  export const GROUNDING_TOOL = join(dirname(fileURLToPath(import.meta.url)), 'grounding.mjs');
272
275
  export const REVIEW_ROUNDS_TOOL = join(dirname(fileURLToPath(import.meta.url)), 'review-rounds-cli.mjs');
273
276
  export const REPO_SEARCH_TOOL = join(dirname(fileURLToPath(import.meta.url)), 'repo-search.mjs');
277
+ export const FEEDBACK_RECORD_TOOL = join(dirname(fileURLToPath(import.meta.url)), 'feedback-record-cli.mjs');
278
+ const FEEDBACK_TRIAGE = 'feedback-triage';
274
279
  const GROUNDING_FACTS_OUT = '/tmp/review-facts.md';
275
280
  const MINTS_RECEIPT = Object.freeze({ mintsReceipt: true });
281
+ const FEEDBACK_BRIDGE_LINES = Object.freeze({
282
+ 'codex-review': 'codex-review plan <record>',
283
+ 'agy-review': 'agy-review plan <record> --facts @<facts>',
284
+ });
285
+ const FEEDBACK_LENS_LINE = 'the Agent tool, read-only, in the background — the brief carries <record> plus <facts>';
286
+ const mintsReceipts = (review) => review.roster == null
287
+ ? (review.backends ?? []).length > 0 || REVIEW_RECIPES.has(review.degradedFrom)
288
+ : review.roster.some((row) => row.kind === 'bridge');
289
+
290
+ const renderFeedbackTriageAdvice = (activity, slots) => {
291
+ if (activity !== FEEDBACK_TRIAGE) return [];
292
+ const review = slots.find((slot) => slot.slot === 'review');
293
+ const members = review.roster == null
294
+ ? review.backends.map((name) => ` then: ${FEEDBACK_BRIDGE_LINES[name]}`)
295
+ : review.roster.map((row) => !isReadyMember(row)
296
+ ? ` then: ${row.member} — ${skippedLine(row)}`
297
+ : row.kind === 'bridge'
298
+ ? ` then: ${FEEDBACK_BRIDGE_LINES[row.member]}`
299
+ : ` then: ${row.member} — ${FEEDBACK_LENS_LINE}`);
300
+ return [
301
+ ` run: node ${renderToolPath(FEEDBACK_RECORD_TOOL)} --check <record> --excerpts <facts>`,
302
+ ...members,
303
+ ...(mintsReceipts(review)
304
+ ? [
305
+ ` then: node ${renderToolPath(REVIEW_ROUNDS_TOOL)} --artifact <record> --activity feedback-triage`,
306
+ ROUND_RENDER_FACT_LINE,
307
+ ]
308
+ : [ROUND_RENDER_NO_BRIDGE_LINE]),
309
+ ];
310
+ };
276
311
 
277
312
  // The round table judges what review-rounds-cli resolves from the config (S27), never this render's
278
313
  // override or degradation, so the advisor states that fact beneath the command instead of predicting
@@ -285,10 +320,7 @@ const roundRenderAdvice = (slots, plans) => {
285
320
  const review = slots.find((s) => s.slot === 'review');
286
321
  if (review === undefined) return [];
287
322
  const roster = review.roster ?? null;
288
- const shouldRenderCommand = roster === null
289
- ? (review.backends ?? []).length > 0 || REVIEW_RECIPES.has(review.degradedFrom)
290
- : roster.some((row) => row.kind === 'bridge');
291
- if (!shouldRenderCommand) return [ROUND_RENDER_NO_BRIDGE_LINE];
323
+ if (!mintsReceipts(review)) return [ROUND_RENDER_NO_BRIDGE_LINE];
292
324
  const lensMembers = (roster ?? []).filter((row) => row.kind === 'lens').map((row) => row.member);
293
325
  const operand = populatedPlan(plans, MINTS_RECEIPT);
294
326
  return [
@@ -345,6 +377,7 @@ const slotSkewWarning = (section, activity) => {
345
377
  return [`the installed engine's canon lists slots (${listed.join(', ')}) for ${activity} while this kit's registry names (${registry.join(', ')}) — the resolved lines follow the registry; the engine and this kit are out of step — /agent-workflow-kit status names which member is behind: upgrade that one. Tell the user.`];
346
378
  };
347
379
  const groundingPreStepAdvice = (activity, slots, plans) => {
380
+ if (activity === FEEDBACK_TRIAGE) return [];
348
381
  if (!slots.some((s) => (s.backends ?? []).includes('agy-review'))) return [];
349
382
  const operand = populatedPlan(plans);
350
383
  const planArg = operand === null ? '--plan <path>' : `--plan ${operand}`;
@@ -621,6 +654,7 @@ const formatHuman = ({ activity, section, slots, warnings, plans, autonomy, flow
621
654
  ? ` ${parallelLine({ value: s.recipe, carrier })} — ${SOURCE_LABEL[s.source]}`
622
655
  : ` ${s.slot}: ${s.recipe} — ${SOURCE_LABEL[s.source]}${arrow}${backendSetLabel(s.backends)}`);
623
656
  if (s.reason) lines.push(` ↳ ${s.reason}`);
657
+ if (s.slot === 'review') lines.push(...renderFeedbackTriageAdvice(activity, slots));
624
658
  // The form replaces the one-line vehicle mention: a carrier never told how to carry is a name,
625
659
  // not an instruction. Indented like the driving contracts beside it.
626
660
  for (const v of s.vehicles ?? []) {
@@ -664,6 +698,7 @@ const buildJson = ({ activity, section, slots, configSource, warnings, plans, au
664
698
  readersSweep,
665
699
  // ADDITIVE (AD-038): the populated grounding pre-step, structured (empty when agy is not dispatched).
666
700
  groundingPreStep: groundingPreStepAdvice(activity, slots, plans),
701
+ feedbackTriage: renderFeedbackTriageAdvice(activity, slots),
667
702
  // ADDITIVE (cost-tiered execution): the unconditional cost-lane advisory, structured.
668
703
  costLanes: costLanesAdvice(),
669
704
  // ADDITIVE (the fold channel): the finding-scope block, structured (empty outside plan-execution).
@@ -737,7 +772,8 @@ export const main = (argv, ctx = {}) => {
737
772
  `backend detection failed (${(err && err.message) || err}) — treating every bridge as not ready; recipes needing a bridge degrade to solo (the executor vehicle is unaffected).`,
738
773
  ),
739
774
  });
740
- const slots = resolveAllSlots({ activity, config, detection, overrides });
775
+ const surveyLens = ctx.surveyLens ?? ((spec) => surveyReviewVehicle(cwd, spec, ctx));
776
+ const slots = resolveAllSlots({ activity, config, detection, overrides, surveyLens });
741
777
  const warnings = [...detectWarnings, ...collectWarnings(slots), ...slotSkewWarning(section, activity)];
742
778
  const plans = plansInFlight(cwd);
743
779
  // The autonomy facts (AD-044 Plan 4): resolved levels + red-lines from the policy file. A
@@ -34,6 +34,8 @@ export const SCRIPT_PRIORS = Object.freeze([
34
34
  prior('spec-schema.mjs', '4.7.0', '4.7.0', '40b5b038d5ec5ed53c327c6d269d22fe5fa2bed99ae711fbf84306ad047be452'),
35
35
  prior('spec-schema.test.mjs', '4.7.0', '4.7.0', 'fde896419924223e54cfcabfdb1ef5807df5386fac700ed7c1463b6e7f81501b'),
36
36
  prior('check-docs-size.mjs', '4.6.0', '6.0.0', '22d020c3668cdbfb4cbc1f67a8a85b2baa2d0c4956808a50626d37409e81ab38'),
37
+ prior('spec-schema.mjs', '5.0.0', '7.2.1', '6a7d1e836c158baab5a412a1ad76d34891934dc28c88486c264a4b5d46f5892a'),
38
+ prior('spec-schema.test.mjs', '5.0.0', '7.2.1', '698564d7283281be1826019d214df475bb876687915de086818d8f5ece0e0336'),
37
39
  ]);
38
40
 
39
41
  export const digestOf = (bytes) => createHash('sha256').update(bytes).digest('hex');