@dzhechkov/harness-core 0.8.5 → 0.8.10
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/.dz-manifest.json +286 -82
- package/README.md +54 -2
- package/dist/amendment-trace.d.ts.map +1 -1
- package/dist/amendment-trace.js +12 -1
- package/dist/amendment-trace.js.map +1 -1
- package/dist/compounding.d.ts +54 -0
- package/dist/compounding.d.ts.map +1 -1
- package/dist/compounding.js +221 -1
- package/dist/compounding.js.map +1 -1
- package/dist/eta.d.ts +92 -0
- package/dist/eta.d.ts.map +1 -0
- package/dist/eta.js +488 -0
- package/dist/eta.js.map +1 -0
- package/dist/feature-adr-checkpoints.d.ts +11 -2
- package/dist/feature-adr-checkpoints.d.ts.map +1 -1
- package/dist/feature-adr-checkpoints.js +38 -3
- package/dist/feature-adr-checkpoints.js.map +1 -1
- package/dist/feature-adr-decision-recall.d.ts +167 -0
- package/dist/feature-adr-decision-recall.d.ts.map +1 -0
- package/dist/feature-adr-decision-recall.js +519 -0
- package/dist/feature-adr-decision-recall.js.map +1 -0
- package/dist/feature-adr-landing.d.ts +37 -0
- package/dist/feature-adr-landing.d.ts.map +1 -0
- package/dist/feature-adr-landing.js +59 -0
- package/dist/feature-adr-landing.js.map +1 -0
- package/dist/feature-adr-routing.d.ts +58 -23
- package/dist/feature-adr-routing.d.ts.map +1 -1
- package/dist/feature-adr-routing.js +194 -69
- package/dist/feature-adr-routing.js.map +1 -1
- package/dist/guard-promotion.d.ts +41 -0
- package/dist/guard-promotion.d.ts.map +1 -1
- package/dist/guard-promotion.js +218 -4
- package/dist/guard-promotion.js.map +1 -1
- package/dist/guard-volume.d.ts +108 -0
- package/dist/guard-volume.d.ts.map +1 -0
- package/dist/guard-volume.js +536 -0
- package/dist/guard-volume.js.map +1 -0
- package/dist/guard.d.ts +34 -0
- package/dist/guard.d.ts.map +1 -1
- package/dist/guard.js +113 -4
- package/dist/guard.js.map +1 -1
- package/dist/index.d.ts +18 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/integration-apply.d.ts +25 -0
- package/dist/integration-apply.d.ts.map +1 -0
- package/dist/integration-apply.js +299 -0
- package/dist/integration-apply.js.map +1 -0
- package/dist/integration-evidence.d.ts +46 -0
- package/dist/integration-evidence.d.ts.map +1 -0
- package/dist/integration-evidence.js +44 -0
- package/dist/integration-evidence.js.map +1 -0
- package/dist/integration-probe-worker.d.ts +22 -0
- package/dist/integration-probe-worker.d.ts.map +1 -0
- package/dist/integration-probe-worker.js +334 -0
- package/dist/integration-probe-worker.js.map +1 -0
- package/dist/integrations-verify.d.ts +60 -0
- package/dist/integrations-verify.d.ts.map +1 -0
- package/dist/integrations-verify.js +194 -0
- package/dist/integrations-verify.js.map +1 -0
- package/dist/lesson-generalization.d.ts +29 -0
- package/dist/lesson-generalization.d.ts.map +1 -0
- package/dist/lesson-generalization.js +84 -0
- package/dist/lesson-generalization.js.map +1 -0
- package/dist/loop-blobs.generated.js +8 -8
- package/dist/loop-blobs.generated.js.map +1 -1
- package/dist/loop-plan.d.ts +13 -1
- package/dist/loop-plan.d.ts.map +1 -1
- package/dist/loop-plan.js +15 -1
- package/dist/loop-plan.js.map +1 -1
- package/dist/loop-render.d.ts.map +1 -1
- package/dist/loop-render.js +51 -6
- package/dist/loop-render.js.map +1 -1
- package/dist/loop-trace.d.ts +20 -1
- package/dist/loop-trace.d.ts.map +1 -1
- package/dist/loop-trace.js +83 -1
- package/dist/loop-trace.js.map +1 -1
- package/dist/model-recommender.d.ts +8 -0
- package/dist/model-recommender.d.ts.map +1 -1
- package/dist/model-recommender.js +31 -4
- package/dist/model-recommender.js.map +1 -1
- package/dist/operations.d.ts +27 -0
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +178 -2
- package/dist/operations.js.map +1 -1
- package/dist/patterns.d.ts +27 -1
- package/dist/patterns.d.ts.map +1 -1
- package/dist/patterns.js +211 -45
- package/dist/patterns.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +20 -17
- package/dist/setup.js.map +1 -1
- package/dist/target-integrations.d.ts +65 -0
- package/dist/target-integrations.d.ts.map +1 -0
- package/dist/target-integrations.js +152 -0
- package/dist/target-integrations.js.map +1 -0
- package/dist/vector-tier.d.ts +6 -1
- package/dist/vector-tier.d.ts.map +1 -1
- package/dist/vector-tier.js +32 -7
- package/dist/vector-tier.js.map +1 -1
- package/dist/workflow-run.d.ts.map +1 -1
- package/dist/workflow-run.js +18 -12
- package/dist/workflow-run.js.map +1 -1
- package/package.json +19 -15
- package/sbom.json +591 -81
- package/src/amendment-trace.ts +12 -1
- package/src/compounding.ts +300 -1
- package/src/eta.ts +590 -0
- package/src/feature-adr-checkpoints.ts +39 -3
- package/src/feature-adr-decision-recall.ts +652 -0
- package/src/feature-adr-landing.ts +109 -0
- package/src/feature-adr-routing.ts +224 -65
- package/src/guard-promotion.ts +245 -4
- package/src/guard-volume.ts +752 -0
- package/src/guard.ts +148 -4
- package/src/index.ts +65 -3
- package/src/integration-apply.ts +332 -0
- package/src/integration-evidence.ts +89 -0
- package/src/integration-probe-worker.ts +310 -0
- package/src/integration-receipts/claude-code/mcp/2.1.235.json +35 -0
- package/src/integrations-verify.ts +258 -0
- package/src/lesson-generalization.ts +115 -0
- package/src/loop-blobs.generated.ts +8 -8
- package/src/loop-plan.ts +36 -3
- package/src/loop-render.ts +50 -6
- package/src/loop-trace.ts +91 -2
- package/src/model-recommender.ts +35 -4
- package/src/operations.ts +199 -2
- package/src/patterns.ts +252 -43
- package/src/setup.ts +20 -17
- package/src/target-integrations.ts +225 -0
- package/src/vector-tier.ts +44 -14
- package/src/workflow-run.ts +18 -11
package/src/model-recommender.ts
CHANGED
|
@@ -92,7 +92,9 @@ export function harvestStageOutcomes(records: readonly unknown[]): Harvest {
|
|
|
92
92
|
if (!isRecord(modelsUsed)) { skipped.noModels += 1; continue; }
|
|
93
93
|
const grade = extractGrade(result);
|
|
94
94
|
if (grade === null) { skipped.noGrade += 1; continue; }
|
|
95
|
-
|
|
95
|
+
// Full canonical JSON is intentional: the former 40-character prefix merged distinct records
|
|
96
|
+
// whose stage names shared a long prefix, manufacturing idempotency across different runs.
|
|
97
|
+
const runId = typeof source['runId'] === 'string' ? source['runId'] : `models:${JSON.stringify(modelsUsed)}`;
|
|
96
98
|
const ts = typeof source['timestamp'] === 'string' ? source['timestamp'] : null;
|
|
97
99
|
const tier = typeof result['tier'] === 'string' && result['tier'] !== '' ? result['tier'] : 'unknown';
|
|
98
100
|
let contributed = false;
|
|
@@ -138,6 +140,8 @@ export interface Recommendation {
|
|
|
138
140
|
readonly rule: string;
|
|
139
141
|
readonly skipped: Harvest['skipped'];
|
|
140
142
|
readonly crossFamilyNote: string;
|
|
143
|
+
readonly unfed: { readonly count: number; readonly runIds: string[] };
|
|
144
|
+
readonly freshness: 'current' | 'stale' | 'unfed';
|
|
141
145
|
};
|
|
142
146
|
}
|
|
143
147
|
|
|
@@ -154,7 +158,7 @@ const CROSS_NOTE =
|
|
|
154
158
|
'the qe pick is computed with the family FORCED to the cross of the code pick — a same-family qe recommendation is unrepresentable (ADR-001 D3)';
|
|
155
159
|
|
|
156
160
|
/** Recommend per stage over the harvested samples (optionally one tier's slice). */
|
|
157
|
-
export function recommendModels(harvest: Harvest, opts: { tier?: string; qualityBar?: number; minSamples?: number } = {}): Recommendation {
|
|
161
|
+
export function recommendModels(harvest: Harvest, opts: { tier?: string; qualityBar?: number; minSamples?: number; alreadyFed?: readonly string[] } = {}): Recommendation {
|
|
158
162
|
const slice = opts.tier === undefined ? harvest.samples : harvest.samples.filter((s) => s.tier === opts.tier);
|
|
159
163
|
const tierLabel = opts.tier ?? 'all';
|
|
160
164
|
const byStage = new Map<string, HarvestSample[]>();
|
|
@@ -197,9 +201,21 @@ export function recommendModels(harvest: Harvest, opts: { tier?: string; quality
|
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
203
|
if (!stages.includes('code')) perStage.unshift(code);
|
|
204
|
+
const pending = unfedRuns(harvest.samples, opts.alreadyFed ?? []);
|
|
205
|
+
const harvestRunIds = [...new Set(harvest.samples.map((s) => s.runId))];
|
|
206
|
+
const fed = new Set(opts.alreadyFed ?? []);
|
|
207
|
+
const fedInHarvest = harvestRunIds.filter((runId) => fed.has(runId)).length;
|
|
200
208
|
return {
|
|
201
209
|
perStage,
|
|
202
|
-
basis: {
|
|
210
|
+
basis: {
|
|
211
|
+
runsUsed: harvest.runsUsed,
|
|
212
|
+
window: harvest.window,
|
|
213
|
+
rule: harvest.rule,
|
|
214
|
+
skipped: harvest.skipped,
|
|
215
|
+
crossFamilyNote: CROSS_NOTE,
|
|
216
|
+
unfed: { count: pending.length, runIds: pending },
|
|
217
|
+
freshness: pending.length === 0 ? 'current' : fedInHarvest === 0 ? 'unfed' : 'stale',
|
|
218
|
+
},
|
|
203
219
|
};
|
|
204
220
|
}
|
|
205
221
|
|
|
@@ -213,15 +229,30 @@ export interface FeedPlan {
|
|
|
213
229
|
readonly fedAfter: string[];
|
|
214
230
|
}
|
|
215
231
|
|
|
232
|
+
/** One shared definition of the telemetry/store gap, used by recommendation, apply and guard. */
|
|
233
|
+
export function unfedRuns(samples: readonly HarvestSample[], alreadyFed: readonly string[]): string[] {
|
|
234
|
+
const fed = new Set(alreadyFed);
|
|
235
|
+
return [...new Set(samples.map((s) => s.runId))].filter((runId) => !fed.has(runId)).sort();
|
|
236
|
+
}
|
|
237
|
+
|
|
216
238
|
/** A run feeds ONCE. Double-feeding the same telemetry manufactures confidence the data does not
|
|
217
239
|
* contain — the second `--apply` must feed 0 and say which runs it skipped. */
|
|
218
240
|
export function planFeed(samples: readonly HarvestSample[], alreadyFed: readonly string[]): FeedPlan {
|
|
219
241
|
const fed = new Set(alreadyFed);
|
|
220
242
|
const toFeed: HarvestSample[] = [];
|
|
221
243
|
const skippedRuns = new Set<string>();
|
|
244
|
+
const seenSamples = new Set<string>();
|
|
222
245
|
for (const s of samples) {
|
|
223
246
|
if (fed.has(s.runId)) skippedRuns.add(s.runId);
|
|
224
|
-
else
|
|
247
|
+
else {
|
|
248
|
+
// A multi-stage run legitimately feeds one sample per stage. Only a repeated sample from a
|
|
249
|
+
// duplicated record is suppressed within this harvest.
|
|
250
|
+
const sampleKey = JSON.stringify([s.runId, s.stage]);
|
|
251
|
+
if (!seenSamples.has(sampleKey)) {
|
|
252
|
+
seenSamples.add(sampleKey);
|
|
253
|
+
toFeed.push(s);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
225
256
|
}
|
|
226
257
|
const fedAfter = [...new Set([...alreadyFed, ...toFeed.map((s) => s.runId)])].sort();
|
|
227
258
|
return { toFeed, skippedRuns: [...skippedRuns].sort(), fedAfter };
|
package/src/operations.ts
CHANGED
|
@@ -57,6 +57,17 @@ import { describeSkillLoadFailure, discoverSkillIds, loadSkillFromDir } from './
|
|
|
57
57
|
import type { SkillApplyFailure, SkillLoadFailure } from './skills.js';
|
|
58
58
|
import { TARGETS } from './targets.js';
|
|
59
59
|
import type { TargetName } from './targets.js';
|
|
60
|
+
import {
|
|
61
|
+
TARGET_INTEGRATIONS,
|
|
62
|
+
aggregateIntegrationManifests,
|
|
63
|
+
notRequestedOutcomes,
|
|
64
|
+
refusedOutcome,
|
|
65
|
+
staticPolicyOutcomes,
|
|
66
|
+
type IntegrationOutcome,
|
|
67
|
+
type IntegrationManifestSource,
|
|
68
|
+
} from './target-integrations.js';
|
|
69
|
+
import { applyIntegrationFragments, IntegrationApplyError, type IntegrationApplyFault } from './integration-apply.js';
|
|
70
|
+
import { verifyTargetIntegration, type IntegrationProcessPort } from './integrations-verify.js';
|
|
60
71
|
import {
|
|
61
72
|
AGENTS_MD_BUDGET_WARN_FRACTION,
|
|
62
73
|
CODEX_PROJECT_DOC_MAX_BYTES,
|
|
@@ -87,6 +98,24 @@ export interface InitOptions {
|
|
|
87
98
|
readonly select?: readonly string[];
|
|
88
99
|
/** When set, generate platform-specific enrichment files alongside SKILL.md. */
|
|
89
100
|
readonly enrich?: boolean;
|
|
101
|
+
/** Exact content-bound digest printed by the first integration-aware run. */
|
|
102
|
+
readonly allowIntegrations?: string;
|
|
103
|
+
/** Explicit hook opt-out; maps hooks to not-requested. */
|
|
104
|
+
readonly noHooks?: boolean;
|
|
105
|
+
/** Explicit skills-only opt-out, even when selected skills carry manifests. */
|
|
106
|
+
readonly noIntegrations?: boolean;
|
|
107
|
+
/** Live verification is load-bearing; requesting this flag makes requested integrations refuse. */
|
|
108
|
+
readonly noVerify?: boolean;
|
|
109
|
+
/** Injectable process boundary for deterministic integration tests. */
|
|
110
|
+
readonly integrationProcessPort?: IntegrationProcessPort;
|
|
111
|
+
/** Test-only fault at the carrier/ownership-journal durability boundary. */
|
|
112
|
+
readonly integrationApplyFault?: IntegrationApplyFault;
|
|
113
|
+
/**
|
|
114
|
+
* Install-level manifest sources. The CLI supplies this only on the first
|
|
115
|
+
* per-directory run so companion planning, authorization, and probing happen
|
|
116
|
+
* exactly once across every discovered skill pack.
|
|
117
|
+
*/
|
|
118
|
+
readonly integrationManifestSources?: readonly IntegrationManifestSource[];
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
/** Per-skill outcome of {@link runInit}. */
|
|
@@ -130,6 +159,10 @@ export interface InitReport {
|
|
|
130
159
|
* any per-skill loop.
|
|
131
160
|
*/
|
|
132
161
|
readonly applyFailures: readonly SkillApplyFailure[];
|
|
162
|
+
/** Exactly two ordered companion outcomes: MCP, then hooks. */
|
|
163
|
+
readonly integrations: readonly [IntegrationOutcome, IntegrationOutcome];
|
|
164
|
+
/** Safe digest used by `--allow-integrations`; absent when no manifest exists. */
|
|
165
|
+
readonly integrationDigest?: string;
|
|
133
166
|
}
|
|
134
167
|
|
|
135
168
|
// ---------------------------------------------------------------------------
|
|
@@ -266,6 +299,8 @@ export interface AgentsMdInitOptions {
|
|
|
266
299
|
readonly projectRoot: string;
|
|
267
300
|
/** When set, install only these skill ids (a preset selection). */
|
|
268
301
|
readonly select?: readonly string[] | undefined;
|
|
302
|
+
readonly noHooks?: boolean;
|
|
303
|
+
readonly noIntegrations?: boolean;
|
|
269
304
|
}
|
|
270
305
|
|
|
271
306
|
/** The parts of a single-file managed-Markdown target that vary by filename. */
|
|
@@ -367,6 +402,28 @@ function runInitSingleFileMd(options: AgentsMdInitOptions, config: SingleFileMdC
|
|
|
367
402
|
}
|
|
368
403
|
const missing = selection === undefined ? [] : selection.filter((id) => !discovered.has(id));
|
|
369
404
|
|
|
405
|
+
let aggregate = { manifest: undefined, digest: undefined } as ReturnType<typeof aggregateIntegrationManifests>;
|
|
406
|
+
try {
|
|
407
|
+
if (options.noIntegrations !== true) {
|
|
408
|
+
aggregate = aggregateIntegrationManifests(picked.map(({ id, skillsDir }) => ({ skillId: id, skillDir: skillsDir })));
|
|
409
|
+
}
|
|
410
|
+
} catch (error) {
|
|
411
|
+
const remediation = error instanceof Error ? error.message : String(error);
|
|
412
|
+
return {
|
|
413
|
+
target: config.target,
|
|
414
|
+
skillsDir: joinedDir,
|
|
415
|
+
projectRoot: options.projectRoot,
|
|
416
|
+
skills: [],
|
|
417
|
+
missing,
|
|
418
|
+
failures: [],
|
|
419
|
+
applyFailures: [],
|
|
420
|
+
integrations: [
|
|
421
|
+
refusedOutcome(config.target, 'mcp', 'MANIFEST_INVALID', remediation),
|
|
422
|
+
refusedOutcome(config.target, 'hooks', 'MANIFEST_INVALID', remediation),
|
|
423
|
+
],
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
370
427
|
// Skip-and-collect: one unloadable skill must not discard the whole aggregation.
|
|
371
428
|
const failures: SkillLoadFailure[] = [];
|
|
372
429
|
const loaded: { id: string; section: string }[] = [];
|
|
@@ -397,7 +454,19 @@ function runInitSingleFileMd(options: AgentsMdInitOptions, config: SingleFileMdC
|
|
|
397
454
|
|
|
398
455
|
// No apply failures are possible here: the single write is outside every per-skill
|
|
399
456
|
// loop, so a write error propagates as itself rather than being attributed to a skill.
|
|
400
|
-
return {
|
|
457
|
+
return {
|
|
458
|
+
target: config.target,
|
|
459
|
+
skillsDir: joinedDir,
|
|
460
|
+
projectRoot: options.projectRoot,
|
|
461
|
+
skills,
|
|
462
|
+
missing,
|
|
463
|
+
failures,
|
|
464
|
+
applyFailures: [],
|
|
465
|
+
integrations: options.noIntegrations === true
|
|
466
|
+
? notRequestedOutcomes(config.target)
|
|
467
|
+
: staticPolicyOutcomes(config.target, aggregate.manifest, options.noHooks === true),
|
|
468
|
+
...(aggregate.digest !== undefined ? { integrationDigest: aggregate.digest } : {}),
|
|
469
|
+
};
|
|
401
470
|
}
|
|
402
471
|
|
|
403
472
|
/**
|
|
@@ -546,6 +615,8 @@ export async function runInit(options: InitOptions): Promise<InitReport> {
|
|
|
546
615
|
skillsDirs: [options.skillsDir],
|
|
547
616
|
projectRoot: options.projectRoot,
|
|
548
617
|
select: options.select,
|
|
618
|
+
...(options.noHooks !== undefined ? { noHooks: options.noHooks } : {}),
|
|
619
|
+
...(options.noIntegrations !== undefined ? { noIntegrations: options.noIntegrations } : {}),
|
|
549
620
|
});
|
|
550
621
|
}
|
|
551
622
|
// gemini is likewise a flattening single-file target — aggregate into ONE root
|
|
@@ -555,6 +626,8 @@ export async function runInit(options: InitOptions): Promise<InitReport> {
|
|
|
555
626
|
skillsDirs: [options.skillsDir],
|
|
556
627
|
projectRoot: options.projectRoot,
|
|
557
628
|
select: options.select,
|
|
629
|
+
...(options.noHooks !== undefined ? { noHooks: options.noHooks } : {}),
|
|
630
|
+
...(options.noIntegrations !== undefined ? { noIntegrations: options.noIntegrations } : {}),
|
|
558
631
|
});
|
|
559
632
|
}
|
|
560
633
|
|
|
@@ -568,6 +641,78 @@ export async function runInit(options: InitOptions): Promise<InitReport> {
|
|
|
568
641
|
const missing = selection === undefined
|
|
569
642
|
? []
|
|
570
643
|
: selection.filter((id) => !discovered.includes(id));
|
|
644
|
+
const manifestSources: readonly IntegrationManifestSource[] = options.integrationManifestSources
|
|
645
|
+
?? ids.map((id) => ({ skillId: id, skillDir: options.skillsDir }));
|
|
646
|
+
let aggregate = { manifest: undefined, digest: undefined } as ReturnType<typeof aggregateIntegrationManifests>;
|
|
647
|
+
try {
|
|
648
|
+
if (options.noIntegrations !== true) aggregate = aggregateIntegrationManifests(manifestSources);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
const remediation = error instanceof Error ? error.message : String(error);
|
|
651
|
+
return {
|
|
652
|
+
target: options.target,
|
|
653
|
+
skillsDir: options.skillsDir,
|
|
654
|
+
projectRoot: options.projectRoot,
|
|
655
|
+
skills: [],
|
|
656
|
+
missing,
|
|
657
|
+
failures: [],
|
|
658
|
+
applyFailures: [],
|
|
659
|
+
integrations: [
|
|
660
|
+
refusedOutcome(options.target, 'mcp', 'MANIFEST_INVALID', remediation),
|
|
661
|
+
refusedOutcome(options.target, 'hooks', 'MANIFEST_INVALID', remediation),
|
|
662
|
+
],
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
let integrations: readonly [IntegrationOutcome, IntegrationOutcome] = notRequestedOutcomes(options.target);
|
|
667
|
+
let eligibleClaudePlan: ReturnType<(typeof TARGET_INTEGRATIONS)['claude-code']['plan']> | undefined;
|
|
668
|
+
if (aggregate.manifest !== undefined && options.noIntegrations !== true) {
|
|
669
|
+
if (options.target !== 'claude-code') {
|
|
670
|
+
integrations = staticPolicyOutcomes(options.target, aggregate.manifest, options.noHooks === true);
|
|
671
|
+
} else {
|
|
672
|
+
const mcpRequested = Object.keys(aggregate.manifest.mcpServers ?? {}).length > 0;
|
|
673
|
+
const hooksRequested = (aggregate.manifest.hooks?.length ?? 0) > 0 && options.noHooks !== true;
|
|
674
|
+
let mcpOutcome: IntegrationOutcome = { target: options.target, component: 'mcp', status: 'not-requested', registrations: [] };
|
|
675
|
+
if (mcpRequested) {
|
|
676
|
+
if (options.noVerify === true) {
|
|
677
|
+
mcpOutcome = refusedOutcome(options.target, 'mcp', 'NO_QUALIFYING_LIVE_RECEIPT', '--no-verify cannot authorize integration emission; rerun with live verification enabled');
|
|
678
|
+
} else if (aggregate.digest === undefined || options.allowIntegrations !== aggregate.digest) {
|
|
679
|
+
mcpOutcome = refusedOutcome(options.target, 'mcp', 'INTEGRATION_AUTHORIZATION_REQUIRED', `rerun with --allow-integrations ${aggregate.digest ?? '<missing-digest>'}`);
|
|
680
|
+
} else {
|
|
681
|
+
const plan = TARGET_INTEGRATIONS['claude-code'].plan(aggregate.manifest, { target: 'claude-code' });
|
|
682
|
+
const refusal = plan.refusals.find((row) => row.component === 'mcp');
|
|
683
|
+
if (refusal !== undefined) {
|
|
684
|
+
mcpOutcome = refusedOutcome(options.target, 'mcp', refusal.reasonCode, refusal.remediation);
|
|
685
|
+
} else {
|
|
686
|
+
const preflight = verifyTargetIntegration({
|
|
687
|
+
target: 'claude-code',
|
|
688
|
+
component: 'mcp',
|
|
689
|
+
projectRoot: options.projectRoot,
|
|
690
|
+
phase: 'preflight',
|
|
691
|
+
...(options.integrationProcessPort !== undefined ? { processPort: options.integrationProcessPort } : {}),
|
|
692
|
+
});
|
|
693
|
+
if (!preflight.ok) {
|
|
694
|
+
mcpOutcome = refusedOutcome(options.target, 'mcp', preflight.reasonCode ?? 'LIVE_PROBE_FAILED', preflight.remediation ?? 'live preflight did not qualify');
|
|
695
|
+
} else {
|
|
696
|
+
eligibleClaudePlan = plan;
|
|
697
|
+
mcpOutcome = {
|
|
698
|
+
target: options.target,
|
|
699
|
+
component: 'mcp',
|
|
700
|
+
status: 'emitted',
|
|
701
|
+
registrations: [],
|
|
702
|
+
carrier: { scope: 'project', path: '.mcp.json' },
|
|
703
|
+
...(preflight.runtimeVersion !== undefined ? { runtimeVersion: preflight.runtimeVersion } : {}),
|
|
704
|
+
...(preflight.evidenceVersion !== undefined ? { evidenceVersion: preflight.evidenceVersion } : {}),
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const hookOutcome: IntegrationOutcome = hooksRequested
|
|
711
|
+
? refusedOutcome(options.target, 'hooks', 'NO_ACTIVATION_RECEIPT', 'record a nonce canary and negative-control activation receipt')
|
|
712
|
+
: { target: options.target, component: 'hooks', status: 'not-requested', registrations: [] };
|
|
713
|
+
integrations = [mcpOutcome, hookOutcome];
|
|
714
|
+
}
|
|
715
|
+
}
|
|
571
716
|
// Skip-and-collect (D1): one unparseable SKILL.md must not discard the whole install.
|
|
572
717
|
// Same shape as `runVerify`'s long-standing per-id try/catch below.
|
|
573
718
|
//
|
|
@@ -602,7 +747,59 @@ export async function runInit(options: InitOptions): Promise<InitReport> {
|
|
|
602
747
|
applyFailures.push({ id, reason: error instanceof Error ? error.message : String(error) });
|
|
603
748
|
}
|
|
604
749
|
}
|
|
605
|
-
|
|
750
|
+
if (eligibleClaudePlan !== undefined && integrations[0].status === 'emitted') {
|
|
751
|
+
try {
|
|
752
|
+
applyIntegrationFragments({
|
|
753
|
+
projectRoot: options.projectRoot,
|
|
754
|
+
fragments: eligibleClaudePlan.fragments,
|
|
755
|
+
...(options.integrationApplyFault !== undefined ? { injectFault: options.integrationApplyFault } : {}),
|
|
756
|
+
});
|
|
757
|
+
const registrations = Object.keys(aggregate.manifest?.mcpServers ?? {});
|
|
758
|
+
const observed = [];
|
|
759
|
+
let failedObservation: ReturnType<typeof verifyTargetIntegration> | undefined;
|
|
760
|
+
for (const registrationId of registrations) {
|
|
761
|
+
const result = verifyTargetIntegration({
|
|
762
|
+
target: 'claude-code',
|
|
763
|
+
component: 'mcp',
|
|
764
|
+
projectRoot: options.projectRoot,
|
|
765
|
+
registrationId,
|
|
766
|
+
phase: 'post-write',
|
|
767
|
+
...(options.integrationProcessPort !== undefined ? { processPort: options.integrationProcessPort } : {}),
|
|
768
|
+
});
|
|
769
|
+
if (!result.ok) { failedObservation = result; break; }
|
|
770
|
+
observed.push(...result.registrations);
|
|
771
|
+
}
|
|
772
|
+
if (failedObservation !== undefined) {
|
|
773
|
+
integrations = [
|
|
774
|
+
{
|
|
775
|
+
...refusedOutcome(options.target, 'mcp', failedObservation.reasonCode ?? 'POST_WRITE_REGISTRATION_NOT_OBSERVED', failedObservation.remediation ?? 'registration was written but not observed'),
|
|
776
|
+
carrier: { scope: 'project', path: '.mcp.json' },
|
|
777
|
+
applied: true,
|
|
778
|
+
},
|
|
779
|
+
integrations[1],
|
|
780
|
+
];
|
|
781
|
+
} else {
|
|
782
|
+
integrations = [{ ...integrations[0], registrations: observed }, integrations[1]];
|
|
783
|
+
}
|
|
784
|
+
} catch (error) {
|
|
785
|
+
const reasonCode = error instanceof IntegrationApplyError ? error.reasonCode : 'APPLY_FAILED';
|
|
786
|
+
integrations = [{
|
|
787
|
+
...refusedOutcome(options.target, 'mcp', reasonCode, error instanceof Error ? error.message : String(error)),
|
|
788
|
+
...(error instanceof IntegrationApplyError && error.applied ? { applied: true } : {}),
|
|
789
|
+
}, integrations[1]];
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return {
|
|
793
|
+
target: options.target,
|
|
794
|
+
skillsDir: options.skillsDir,
|
|
795
|
+
projectRoot: options.projectRoot,
|
|
796
|
+
skills,
|
|
797
|
+
missing,
|
|
798
|
+
failures,
|
|
799
|
+
applyFailures,
|
|
800
|
+
integrations,
|
|
801
|
+
...(aggregate.digest !== undefined ? { integrationDigest: aggregate.digest } : {}),
|
|
802
|
+
};
|
|
606
803
|
}
|
|
607
804
|
|
|
608
805
|
// ---------------------------------------------------------------------------
|