@mcptoolshop/research-os 0.2.0 → 0.3.1

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.
package/dist/index.js CHANGED
@@ -112,7 +112,7 @@ var init_errors = __esm({
112
112
 
113
113
  // src/intake/schema.ts
114
114
  import { z } from "zod";
115
- var SectionStatusSchema, SectionSchema, SourceFloorGateSchema, ClaimIntegrityGateSchema, FreshnessGateSchema, ContradictionGateSchema, SectionBudgetGateSchema, GateConfigSchema, PrimarySourceWaiverSchema, FreshnessRequirementsSchema, ReviewProfilePresetSchema, DEFAULT_REVIEW_PROFILES, ResearchYamlSchema;
115
+ var SectionStatusSchema, SectionSchema, SourceFloorGateSchema, ClaimIntegrityGateSchema, FreshnessGateSchema, ContradictionGateSchema, SectionBudgetGateSchema, GateConfigSchema, SectionScopedWaiverSchema, PrimarySourceWaiverSchema, FreshnessRequirementsSchema, ReviewProfilePresetSchema, DEFAULT_REVIEW_PROFILES, ResearchYamlSchema;
116
116
  var init_schema = __esm({
117
117
  "src/intake/schema.ts"() {
118
118
  "use strict";
@@ -162,10 +162,20 @@ var init_schema = __esm({
162
162
  contradiction: ContradictionGateSchema.default({}),
163
163
  section_budget: SectionBudgetGateSchema.default({})
164
164
  });
165
+ SectionScopedWaiverSchema = z.object({
166
+ section_id: z.string().regex(/^[0-9]{2}-[a-z0-9-]+$/, 'Section id must look like "01-landscape"'),
167
+ scope: z.enum(["min_independent_publishers", "primary_sources_required"]),
168
+ reason: z.string().min(1),
169
+ compensating_controls: z.array(z.string()).min(1)
170
+ });
165
171
  PrimarySourceWaiverSchema = z.object({
166
172
  status: z.enum(["none", "requested", "granted"]).default("none"),
167
173
  reason: z.string().optional(),
168
- compensating_controls: z.array(z.string()).default([])
174
+ compensating_controls: z.array(z.string()).default([]),
175
+ // Section-scoped waivers; each entry is its own waiver record. Independent
176
+ // of the pack-level status/reason/compensating_controls fields above.
177
+ // Defaults to [] for backward compatibility — existing packs unaffected.
178
+ section_waivers: z.array(SectionScopedWaiverSchema).default([])
169
179
  });
170
180
  FreshnessRequirementsSchema = z.object({
171
181
  required: z.boolean().default(true),
@@ -3262,6 +3272,48 @@ function buildContradiction(args) {
3262
3272
  created_at: (/* @__PURE__ */ new Date()).toISOString()
3263
3273
  });
3264
3274
  }
3275
+ async function resolveDetector(options) {
3276
+ const mode = options.detectorMode ?? "auto";
3277
+ if (!VALID_DETECTOR_MODES.includes(mode)) {
3278
+ throw new Error(
3279
+ `contradict map: invalid --detector value "${mode}"; valid values are: auto, heuristic, ollama-intern`
3280
+ );
3281
+ }
3282
+ if (mode === "heuristic") {
3283
+ return {
3284
+ detector: new HeuristicContradictionDetector(),
3285
+ announcement: "contradict map: using heuristic detector"
3286
+ };
3287
+ }
3288
+ if (mode === "ollama-intern") {
3289
+ const d = new OllamaInternContradictionDetector(options.ollamaConfig ?? {});
3290
+ if (!await d.available()) {
3291
+ throw new Error(
3292
+ `contradict map: ollama-intern detector requested but model ${d.model} is unavailable; aborting (use --detector heuristic to bypass)`
3293
+ );
3294
+ }
3295
+ return {
3296
+ detector: d,
3297
+ announcement: `contradict map: using ollama-intern detector with model ${d.model}`
3298
+ };
3299
+ }
3300
+ const detectors = options.detectors ?? [
3301
+ new OllamaInternContradictionDetector(options.ollamaConfig ?? {}),
3302
+ new HeuristicContradictionDetector()
3303
+ ];
3304
+ const detector = await pickContradictionDetector(detectors);
3305
+ if (detector.name === "ollama-intern") {
3306
+ const modelName = detector instanceof OllamaInternContradictionDetector ? detector.model : process.env.OLLAMA_INTERN_MODEL ?? "hermes3:8b";
3307
+ return {
3308
+ detector,
3309
+ announcement: `contradict map: using ollama-intern detector with model ${modelName}`
3310
+ };
3311
+ }
3312
+ return {
3313
+ detector,
3314
+ announcement: "contradict map: ollama-intern unavailable; using heuristic detector"
3315
+ };
3316
+ }
3265
3317
  async function map(options) {
3266
3318
  const packPath = options.packPath ? resolve7(options.packPath) : process.cwd();
3267
3319
  if (!existsSync9(join10(packPath, "research.yaml"))) throw new PackNotFoundError(packPath);
@@ -3273,8 +3325,7 @@ async function map(options) {
3273
3325
  const allowed = await readTriagedClaimIds2(packPath, options.sectionId);
3274
3326
  candidateClaims = candidateClaims.filter((c) => allowed.has(c.claim_id));
3275
3327
  }
3276
- const adapters = options.detectors ?? defaultContradictionDetectors();
3277
- const detector = await pickContradictionDetector(adapters);
3328
+ const { detector, announcement } = await resolveDetector(options);
3278
3329
  const summary = {
3279
3330
  sectionId: options.sectionId,
3280
3331
  detector: detector.name,
@@ -3284,7 +3335,8 @@ async function map(options) {
3284
3335
  contradictionsAdded: 0,
3285
3336
  contradictionsDeduped: 0,
3286
3337
  contradictionIds: [],
3287
- detectorError: null
3338
+ detectorError: null,
3339
+ detectorAnnouncement: announcement
3288
3340
  };
3289
3341
  const ledgerPath = join10(sectionDir, "contradictions.jsonl");
3290
3342
  const mdPath = join10(sectionDir, "contradictions.md");
@@ -3348,7 +3400,7 @@ async function map(options) {
3348
3400
  await writeFile9(mdPath, md, "utf8");
3349
3401
  return summary;
3350
3402
  }
3351
- var DETECTOR_ID_PART;
3403
+ var DETECTOR_ID_PART, VALID_DETECTOR_MODES;
3352
3404
  var init_map = __esm({
3353
3405
  "src/contradictions/map.ts"() {
3354
3406
  "use strict";
@@ -3356,11 +3408,14 @@ var init_map = __esm({
3356
3408
  init_schema5();
3357
3409
  init_schema7();
3358
3410
  init_detectors();
3411
+ init_heuristic3();
3412
+ init_ollama_intern3();
3359
3413
  init_markdown();
3360
3414
  DETECTOR_ID_PART = {
3361
3415
  heuristic: "heuristic",
3362
3416
  "ollama-intern": "ollama_intern"
3363
3417
  };
3418
+ VALID_DETECTOR_MODES = ["auto", "heuristic", "ollama-intern"];
3364
3419
  }
3365
3420
  });
3366
3421
 
@@ -4177,9 +4232,28 @@ function applyWaivers(input, results) {
4177
4232
  const updated = results.map((r) => ({ ...r }));
4178
4233
  const applied = [];
4179
4234
  const validationFailures = [];
4180
- if (waiver.status !== "granted") {
4181
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4235
+ applyPackLevelWaiver({
4236
+ waiver,
4237
+ cfg,
4238
+ updated,
4239
+ applied,
4240
+ validationFailures
4241
+ });
4242
+ for (const sw of waiver.section_waivers ?? []) {
4243
+ if (sw.section_id !== input.section.id) continue;
4244
+ applySectionScopedWaiver({
4245
+ waiver: sw,
4246
+ cfg,
4247
+ updated,
4248
+ applied,
4249
+ validationFailures
4250
+ });
4182
4251
  }
4252
+ return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4253
+ }
4254
+ function applyPackLevelWaiver(args) {
4255
+ const { waiver, cfg, updated, applied, validationFailures } = args;
4256
+ if (waiver.status !== "granted") return;
4183
4257
  if (!waiver.reason || waiver.reason.trim().length === 0) {
4184
4258
  validationFailures.push({
4185
4259
  family: "waivers",
@@ -4189,7 +4263,7 @@ function applyWaivers(input, results) {
4189
4263
  evidence: [],
4190
4264
  blocks_synthesis: true
4191
4265
  });
4192
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4266
+ return;
4193
4267
  }
4194
4268
  if (waiver.compensating_controls.length === 0) {
4195
4269
  validationFailures.push({
@@ -4200,7 +4274,7 @@ function applyWaivers(input, results) {
4200
4274
  evidence: [],
4201
4275
  blocks_synthesis: true
4202
4276
  });
4203
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4277
+ return;
4204
4278
  }
4205
4279
  if (!cfg.primary_source_waiver_allowed) {
4206
4280
  validationFailures.push({
@@ -4211,7 +4285,7 @@ function applyWaivers(input, results) {
4211
4285
  evidence: [],
4212
4286
  blocks_synthesis: true
4213
4287
  });
4214
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4288
+ return;
4215
4289
  }
4216
4290
  for (let i = 0; i < updated.length; i += 1) {
4217
4291
  const r = updated[i];
@@ -4233,7 +4307,62 @@ function applyWaivers(input, results) {
4233
4307
  });
4234
4308
  }
4235
4309
  }
4236
- return { updatedResults: updated, waivers_applied: applied, waiver_validation_failures: validationFailures };
4310
+ }
4311
+ function applySectionScopedWaiver(args) {
4312
+ const { waiver, cfg, updated, applied, validationFailures } = args;
4313
+ if (!waiver.reason || waiver.reason.trim().length === 0) {
4314
+ validationFailures.push({
4315
+ family: "waivers",
4316
+ check: "section_scoped_waiver_reason_required",
4317
+ status: "fail",
4318
+ detail: `Section-scoped waiver for ${waiver.section_id}/${waiver.scope} is missing a reason. Waiver is invalid; original failure stands.`,
4319
+ evidence: [waiver.section_id],
4320
+ blocks_synthesis: true
4321
+ });
4322
+ return;
4323
+ }
4324
+ if (waiver.compensating_controls.length === 0) {
4325
+ validationFailures.push({
4326
+ family: "waivers",
4327
+ check: "section_scoped_waiver_compensating_controls_required",
4328
+ status: "fail",
4329
+ detail: `Section-scoped waiver for ${waiver.section_id}/${waiver.scope} has empty compensating_controls. Waiver is invalid; original failure stands.`,
4330
+ evidence: [waiver.section_id],
4331
+ blocks_synthesis: true
4332
+ });
4333
+ return;
4334
+ }
4335
+ if (!cfg.primary_source_waiver_allowed) {
4336
+ validationFailures.push({
4337
+ family: "waivers",
4338
+ check: "section_scoped_waiver_allowed_by_pack",
4339
+ status: "fail",
4340
+ detail: `Pack policy gates.source_floor.primary_source_waiver_allowed=false; section-scoped waiver for ${waiver.section_id}/${waiver.scope} cannot be applied.`,
4341
+ evidence: [waiver.section_id],
4342
+ blocks_synthesis: true
4343
+ });
4344
+ return;
4345
+ }
4346
+ for (let i = 0; i < updated.length; i += 1) {
4347
+ const r = updated[i];
4348
+ if (r.family === "source_floor" && r.check === waiver.scope && r.status === "fail") {
4349
+ const original = r.status;
4350
+ updated[i] = {
4351
+ ...r,
4352
+ status: "pass_with_waiver",
4353
+ detail: `${r.detail} Section-scoped waiver granted for ${waiver.section_id} with ${waiver.compensating_controls.length} compensating control(s); converted from fail to pass_with_waiver.`,
4354
+ blocks_synthesis: false
4355
+ };
4356
+ applied.push({
4357
+ family: "source_floor",
4358
+ check: waiver.scope,
4359
+ reason: waiver.reason,
4360
+ compensating_controls: waiver.compensating_controls,
4361
+ original_status: original,
4362
+ new_status: "pass_with_waiver"
4363
+ });
4364
+ }
4365
+ }
4237
4366
  }
4238
4367
  var init_waivers = __esm({
4239
4368
  "src/gates/checks/waivers.ts"() {
@@ -5490,9 +5619,11 @@ function pickHighestPriority(decisions) {
5490
5619
  return "accepted_for_synthesis";
5491
5620
  }
5492
5621
  function deriveClaimReviews(args) {
5493
- const { claims, findings, reviewer, reviewMethod } = args;
5622
+ const { claims, findings, reviewer, reviewMethod, activeSectionWaivers } = args;
5494
5623
  const reviews = [];
5495
5624
  const now = (/* @__PURE__ */ new Date()).toISOString();
5625
+ const monopolyWaived = Array.isArray(activeSectionWaivers) && activeSectionWaivers.some((w) => w.scope === "min_independent_publishers");
5626
+ const isWaivedFinding = (f) => monopolyWaived && f.category === "source_cluster_monopoly";
5496
5627
  for (const claim of claims) {
5497
5628
  const claimFindings = findings.filter((f) => f.claim_ids.includes(claim.claim_id));
5498
5629
  if (claimFindings.length === 0) {
@@ -5509,6 +5640,7 @@ function deriveClaimReviews(args) {
5509
5640
  }
5510
5641
  let decisions = [];
5511
5642
  for (const f of claimFindings) {
5643
+ if (isWaivedFinding(f)) continue;
5512
5644
  if (f.severity === "block") {
5513
5645
  decisions.push(BLOCK_TO_DECISION[f.category] ?? "rejected");
5514
5646
  } else if (f.severity === "warn") {
@@ -5525,7 +5657,9 @@ function deriveClaimReviews(args) {
5525
5657
  );
5526
5658
  }
5527
5659
  const decision = pickHighestPriority(decisions);
5528
- const reasonParts = claimFindings.filter((f) => f.severity !== "info").map((f) => `${f.category} (${f.severity})`);
5660
+ const reasonParts = claimFindings.filter((f) => f.severity !== "info").map(
5661
+ (f) => isWaivedFinding(f) ? `${f.category} (${f.severity}, waived)` : `${f.category} (${f.severity})`
5662
+ );
5529
5663
  const reason = reasonParts.length > 0 ? `Findings: ${reasonParts.join("; ")}.` : "Only info-level findings; accepted.";
5530
5664
  reviews.push({
5531
5665
  claim_id: claim.claim_id,
@@ -5906,7 +6040,8 @@ async function review(options) {
5906
6040
  candidateClaims,
5907
6041
  drafts: acceptedDrafts,
5908
6042
  llmFindingsRejected,
5909
- profile: options.profile ?? DEFAULT_PROFILE
6043
+ profile: options.profile ?? DEFAULT_PROFILE,
6044
+ research
5910
6045
  });
5911
6046
  }
5912
6047
  async function runMultiPassReview(args) {
@@ -5968,7 +6103,8 @@ async function runMultiPassReview(args) {
5968
6103
  candidateClaims: args.candidateClaims,
5969
6104
  drafts: merged,
5970
6105
  llmFindingsRejected,
5971
- profile: args.options.profile ?? DEFAULT_PROFILE
6106
+ profile: args.options.profile ?? DEFAULT_PROFILE,
6107
+ research: args.research
5972
6108
  });
5973
6109
  }
5974
6110
  async function reviewWithSpecificReviewer(args) {
@@ -5996,7 +6132,8 @@ async function reviewWithSpecificReviewer(args) {
5996
6132
  candidateClaims: args.candidateClaims,
5997
6133
  drafts: result.drafts,
5998
6134
  llmFindingsRejected: 0,
5999
- profile: args.options.profile ?? DEFAULT_PROFILE
6135
+ profile: args.options.profile ?? DEFAULT_PROFILE,
6136
+ research: args.research
6000
6137
  });
6001
6138
  }
6002
6139
  async function finalizeReview(args) {
@@ -6019,11 +6156,15 @@ async function finalizeReview(args) {
6019
6156
  seen.add(f.finding_id);
6020
6157
  dedupedFindings.push(f);
6021
6158
  }
6159
+ const activeSectionWaivers = args.research.primary_source_waiver.section_waivers.filter(
6160
+ (w) => w.section_id === args.sectionId
6161
+ );
6022
6162
  const claimReviews = deriveClaimReviews({
6023
6163
  claims: args.candidateClaims,
6024
6164
  findings: dedupedFindings,
6025
6165
  reviewer: args.reviewer,
6026
- reviewMethod: args.reviewMethod
6166
+ reviewMethod: args.reviewMethod,
6167
+ activeSectionWaivers
6027
6168
  });
6028
6169
  const decisionCounts = {
6029
6170
  accepted_for_synthesis: 0,
@@ -8750,6 +8891,19 @@ function buildStaleSources(input) {
8750
8891
  }
8751
8892
  return out;
8752
8893
  }
8894
+ function findSectionWaiver(research, sectionId, scope) {
8895
+ return (research.primary_source_waiver.section_waivers ?? []).find(
8896
+ (w) => w.section_id === sectionId && w.scope === scope
8897
+ );
8898
+ }
8899
+ function annotateWeakSource(row, waiver) {
8900
+ if (!waiver) return row;
8901
+ return { ...row, waived: true, waiver_reason: waiver.reason };
8902
+ }
8903
+ function annotateDiversityGap(row, waiver) {
8904
+ if (!waiver) return row;
8905
+ return { ...row, waived: true, waiver_reason: waiver.reason };
8906
+ }
8753
8907
  function buildWeakSources(input) {
8754
8908
  const out = [];
8755
8909
  const cfg = input.research.gates.source_floor;
@@ -8759,33 +8913,50 @@ function buildWeakSources(input) {
8759
8913
  const publishers = new Set(
8760
8914
  sectionSources.map((c) => c.publisher).filter((p) => typeof p === "string")
8761
8915
  );
8916
+ const monopolyWaiver = findSectionWaiver(input.research, sid, "min_independent_publishers");
8917
+ const primaryWaiver = findSectionWaiver(input.research, sid, "primary_sources_required");
8762
8918
  if (publishers.size === 1 && sectionSources.length >= 2) {
8763
- out.push({
8764
- reason: "source_cluster_monopoly",
8765
- section_id: sid,
8766
- details: `Every source in this section traces to a single publisher (${[...publishers][0]}).`,
8767
- evidence_ids: sectionSources.map((s) => s.source_id),
8768
- artifact_path: `sections/${sid}/sources.jsonl`
8769
- });
8919
+ out.push(
8920
+ annotateWeakSource(
8921
+ {
8922
+ reason: "source_cluster_monopoly",
8923
+ section_id: sid,
8924
+ details: `Every source in this section traces to a single publisher (${[...publishers][0]}).`,
8925
+ evidence_ids: sectionSources.map((s) => s.source_id),
8926
+ artifact_path: `sections/${sid}/sources.jsonl`
8927
+ },
8928
+ monopolyWaiver
8929
+ )
8930
+ );
8770
8931
  }
8771
8932
  if (publishers.size < cfg.min_independent_publishers) {
8772
- out.push({
8773
- reason: "low_independent_publishers",
8774
- section_id: sid,
8775
- details: `${publishers.size} independent publisher(s) \u2014 pack policy requires at least ${cfg.min_independent_publishers}.`,
8776
- evidence_ids: [...publishers],
8777
- artifact_path: `sections/${sid}/sources.jsonl`
8778
- });
8933
+ out.push(
8934
+ annotateWeakSource(
8935
+ {
8936
+ reason: "low_independent_publishers",
8937
+ section_id: sid,
8938
+ details: `${publishers.size} independent publisher(s) \u2014 pack policy requires at least ${cfg.min_independent_publishers}.`,
8939
+ evidence_ids: [...publishers],
8940
+ artifact_path: `sections/${sid}/sources.jsonl`
8941
+ },
8942
+ monopolyWaiver
8943
+ )
8944
+ );
8779
8945
  }
8780
8946
  const primary = sectionSources.filter((c) => c.source_type === "primary").length;
8781
8947
  if (primary < cfg.primary_sources_required) {
8782
- out.push({
8783
- reason: "missing_primary_source",
8784
- section_id: sid,
8785
- details: `${primary} primary source(s) \u2014 pack policy requires at least ${cfg.primary_sources_required}.`,
8786
- evidence_ids: sectionSources.filter((c) => c.source_type === "primary").map((c) => c.source_id),
8787
- artifact_path: `sections/${sid}/sources.jsonl`
8788
- });
8948
+ out.push(
8949
+ annotateWeakSource(
8950
+ {
8951
+ reason: "missing_primary_source",
8952
+ section_id: sid,
8953
+ details: `${primary} primary source(s) \u2014 pack policy requires at least ${cfg.primary_sources_required}.`,
8954
+ evidence_ids: sectionSources.filter((c) => c.source_type === "primary").map((c) => c.source_id),
8955
+ artifact_path: `sections/${sid}/sources.jsonl`
8956
+ },
8957
+ primaryWaiver
8958
+ )
8959
+ );
8789
8960
  }
8790
8961
  const types = /* @__PURE__ */ new Map();
8791
8962
  for (const c of sectionSources) types.set(c.source_type, (types.get(c.source_type) ?? 0) + 1);
@@ -8900,20 +9071,31 @@ function buildSourceDiversityGaps(input) {
8900
9071
  });
8901
9072
  continue;
8902
9073
  }
9074
+ const monopolyWaiver = findSectionWaiver(input.research, sid, "min_independent_publishers");
8903
9075
  if (publishers.size === 1 && sectionSources.length >= 2) {
8904
- out.push({
8905
- reason: "section_publisher_monopoly",
8906
- section_id: sid,
8907
- details: `Section sources monopolized by ${[...publishers][0]}.`,
8908
- evidence_ids: sectionSources.map((s) => s.source_id)
8909
- });
9076
+ out.push(
9077
+ annotateDiversityGap(
9078
+ {
9079
+ reason: "section_publisher_monopoly",
9080
+ section_id: sid,
9081
+ details: `Section sources monopolized by ${[...publishers][0]}.`,
9082
+ evidence_ids: sectionSources.map((s) => s.source_id)
9083
+ },
9084
+ monopolyWaiver
9085
+ )
9086
+ );
8910
9087
  } else if (publishers.size < cfg.min_independent_publishers) {
8911
- out.push({
8912
- reason: "low_section_publisher_count",
8913
- section_id: sid,
8914
- details: `${publishers.size} publisher(s); pack policy requires ${cfg.min_independent_publishers}.`,
8915
- evidence_ids: [...publishers]
8916
- });
9088
+ out.push(
9089
+ annotateDiversityGap(
9090
+ {
9091
+ reason: "low_section_publisher_count",
9092
+ section_id: sid,
9093
+ details: `${publishers.size} publisher(s); pack policy requires ${cfg.min_independent_publishers}.`,
9094
+ evidence_ids: [...publishers]
9095
+ },
9096
+ monopolyWaiver
9097
+ )
9098
+ );
8917
9099
  }
8918
9100
  }
8919
9101
  const pubSectionCount = /* @__PURE__ */ new Map();
@@ -12120,7 +12302,7 @@ var init_src = __esm({
12120
12302
  init_triage();
12121
12303
  init_discover();
12122
12304
  init_errors();
12123
- RESEARCH_OS_VERSION = "0.2.0";
12305
+ RESEARCH_OS_VERSION = "0.3.1";
12124
12306
  }
12125
12307
  });
12126
12308
  init_src();