opencode-swarm 7.136.0 → 7.136.2
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/.opencode/skills/swarm-pr-feedback/SKILL.md +41 -1
- package/.opencode/skills/swarm-pr-review/SKILL.md +83 -24
- package/README.md +18 -0
- package/dist/background/candidate-contract.d.ts +13 -0
- package/dist/background/workspace-snapshot.d.ts +54 -0
- package/dist/cli/{config-doctor-q0krbxwv.js → config-doctor-b67nb84b.js} +2 -2
- package/dist/cli/{curation-policy-v6ywe8qd.js → curation-policy-kvzhfaj1.js} +2 -2
- package/dist/cli/{curator-llm-factory-mhwje8rf.js → curator-llm-factory-x7kvry9x.js} +17 -14
- package/dist/cli/{curator-tqedvpy1.js → curator-nrmj8bds.js} +17 -14
- package/dist/cli/{evidence-summary-service-w006jnpg.js → evidence-summary-service-cf8sz2bq.js} +3 -1
- package/dist/cli/{guardrail-explain-m0m74hvr.js → guardrail-explain-5kd44rcj.js} +18 -15
- package/dist/cli/{guardrail-log-5g8x6qc6.js → guardrail-log-aksrzqdx.js} +3 -3
- package/dist/cli/hashing-mjwn6j4y.js +34 -0
- package/dist/cli/{hive-promoter-s804newd.js → hive-promoter-cy21rhnd.js} +17 -14
- package/dist/cli/{index-n8j0cnp1.js → index-0755132s.js} +4 -4
- package/dist/cli/{index-1kjg590p.js → index-1kn24ja0.js} +1 -1
- package/dist/cli/{index-j08trvny.js → index-21115szq.js} +5 -5
- package/dist/cli/{index-2cv1j3rf.js → index-37v2wxqe.js} +1 -1
- package/dist/cli/{index-cfz5750k.js → index-7ayaq27q.js} +5 -1
- package/dist/cli/{index-9bk16zkv.js → index-84nz7bhe.js} +1 -1
- package/dist/cli/{index-bwfzq91q.js → index-ad6j0m7k.js} +12 -6
- package/dist/cli/{index-qh9466j3.js → index-azghvnja.js} +2 -2
- package/dist/cli/{index-cxq1t7j1.js → index-ckybsn6p.js} +19 -16
- package/dist/cli/{index-cggqh2dz.js → index-kwwxevne.js} +53 -996
- package/dist/cli/{index-nw9cn84t.js → index-m5fw4mer.js} +19 -2
- package/dist/cli/{index-2w4tsmva.js → index-rw3f73aq.js} +1 -1
- package/dist/cli/{index-wpqypdge.js → index-s0rbdp61.js} +2 -2
- package/dist/cli/{index-bbejqagj.js → index-s0vahtdm.js} +1 -1
- package/dist/cli/{index-81dkzncr.js → index-sr2cynyw.js} +4535 -2325
- package/dist/cli/index-wjm896ey.js +1100 -0
- package/dist/cli/{index-w58bmwyq.js → index-wnz282j3.js} +2 -2
- package/dist/cli/{index-y6a7gjtj.js → index-wsdnttf4.js} +228 -81
- package/dist/cli/{index-m1hew17b.js → index-y0xaq4f3.js} +39 -4
- package/dist/cli/index-y8552snf.js +264 -0
- package/dist/cli/index-z1tm47nj.js +783 -0
- package/dist/cli/index.js +17 -14
- package/dist/cli/{knowledge-escalator-f1m2xp5x.js → knowledge-escalator-ta3xjrdj.js} +3 -3
- package/dist/cli/{knowledge-events-rksgem6f.js → knowledge-events-dk6banmz.js} +1 -1
- package/dist/cli/{knowledge-store-t6d6fr8b.js → knowledge-store-h3jz10bv.js} +1 -1
- package/dist/cli/{knowledge-validator-5ggzb3j1.js → knowledge-validator-jsz1dxkr.js} +4 -4
- package/dist/cli/runner-2v413r74.js +21 -0
- package/dist/cli/{scan-cursor-18fnwr58.js → scan-cursor-48gwzh9c.js} +2 -2
- package/dist/cli/{schema-jw15d8bt.js → schema-f937b9cs.js} +5 -1
- package/dist/cli/{skill-generator-3n5y902z.js → skill-generator-4p10ktw1.js} +5 -5
- package/dist/cli/{workspace-snapshot-jmyamqnv.js → workspace-snapshot-h5rzw37b.js} +5 -1
- package/dist/commands/registry.d.ts +72 -0
- package/dist/commands/skill-opt.d.ts +41 -0
- package/dist/config/schema.d.ts +47 -0
- package/dist/hooks/guardrails/file-authority.d.ts +11 -2
- package/dist/hooks/pr-workflow-gate.d.ts +261 -4
- package/dist/hooks/pr-workflow-response-gate.d.ts +27 -9
- package/dist/hooks/write-target-resolver.d.ts +19 -0
- package/dist/index.js +355 -320
- package/dist/services/skill-evaluator.d.ts +17 -0
- package/dist/services/skill-optimizer/activation.d.ts +58 -0
- package/dist/services/skill-optimizer/candidates.d.ts +88 -0
- package/dist/services/skill-optimizer/controller.d.ts +146 -0
- package/dist/services/skill-optimizer/deterministic-seed.d.ts +29 -0
- package/dist/services/skill-optimizer/lifecycle.d.ts +70 -0
- package/dist/services/skill-optimizer/promoted-external-staleness.d.ts +98 -0
- package/dist/services/skill-optimizer/retirement.d.ts +54 -0
- package/dist/services/skill-optimizer/skill-eval-tasks.d.ts +47 -0
- package/dist/services/skill-optimizer/smoke.d.ts +46 -0
- package/dist/services/skill-optimizer/store.d.ts +118 -0
- package/dist/tools/write-pr-review-trigger-eval.d.ts +2 -1
- package/dist/utils/stable-stringify.d.ts +46 -0
- package/evaluation-fixtures/skill-eval/scoring/score-skill-eval.cjs +97 -0
- package/package.json +1 -1
|
@@ -64,6 +64,23 @@ declare function evaluateContent(content: string, testCase: SkillEvalCase): {
|
|
|
64
64
|
score: number;
|
|
65
65
|
failures: string[];
|
|
66
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* Pure phrase-scoring function (issue #1822 — factored out so the governed
|
|
69
|
+
* skill optimizer's `project` scorer wrapper can invoke the SAME scoring logic
|
|
70
|
+
* the internal gate uses, without duplicating it). Behavior is identical to the
|
|
71
|
+
* previous inline implementation in `evaluateContent`.
|
|
72
|
+
*
|
|
73
|
+
* Score = requiredHits / max(1, required.length), minus a 1-point penalty if
|
|
74
|
+
* any forbidden phrase is present, clamped to >= 0.
|
|
75
|
+
*/
|
|
76
|
+
export declare function scoreSkillPhrases(args: {
|
|
77
|
+
content: string;
|
|
78
|
+
required?: string[];
|
|
79
|
+
forbidden?: string[];
|
|
80
|
+
}): {
|
|
81
|
+
score: number;
|
|
82
|
+
failures: string[];
|
|
83
|
+
};
|
|
67
84
|
declare function hashNormalizedContent(content: string | undefined): string | undefined;
|
|
68
85
|
export declare function evaluateSkillChange(req: EvaluateSkillChangeRequest): Promise<SkillEvaluationResult>;
|
|
69
86
|
export declare function appendRejectedSkillEdit(req: EvaluateSkillChangeRequest, evaluation: SkillEvaluationResult): Promise<void>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activation + rollback (Workstream D, issue #1822).
|
|
3
|
+
*
|
|
4
|
+
* Safety model (per the revised plan + critic C2): slash commands invoked by a
|
|
5
|
+
* human do NOT go through scope-guard.ts (which is coder-only, line 83). The
|
|
6
|
+
* activation safety stack is therefore:
|
|
7
|
+
* 1. `toolPolicy: 'human-only'` on the `approve`/`activate`/`reject`/`rollback`
|
|
8
|
+
* command entries — prevents agent `swarm_command` invocation;
|
|
9
|
+
* 2. an explicit `expectedContentHash` argument checked against the current
|
|
10
|
+
* SKILL.md contentHash — refuses a stale base (`STALE_BASE`);
|
|
11
|
+
* 3. atomic write (snapshot-then-rename) after recording a rollback snapshot;
|
|
12
|
+
* 4. append-only history — rollback appends a new event, never deletes.
|
|
13
|
+
*
|
|
14
|
+
* No claim of `SCOPE_NOT_DECLARED` for these commands (that invariant applies
|
|
15
|
+
* to coder tool calls under a Task, not human slash commands).
|
|
16
|
+
*/
|
|
17
|
+
import { renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
18
|
+
export declare const _internals: {
|
|
19
|
+
writeFileSync: typeof writeFileSync;
|
|
20
|
+
renameSync: typeof renameSync;
|
|
21
|
+
unlinkSync: typeof unlinkSync;
|
|
22
|
+
};
|
|
23
|
+
export interface ActivateInput {
|
|
24
|
+
directory: string;
|
|
25
|
+
skillSlug: string;
|
|
26
|
+
candidateId: string;
|
|
27
|
+
actor: string;
|
|
28
|
+
/** Caller's view of the current SKILL.md contentHash — refuses stale base. */
|
|
29
|
+
expectedContentHash: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ActivateResult {
|
|
32
|
+
activated: boolean;
|
|
33
|
+
reason: string;
|
|
34
|
+
/** Path to the rollback snapshot recorded before activation. */
|
|
35
|
+
rollbackSnapshotRef: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Activate a candidate: refuse stale base, snapshot the incumbent, atomic-write
|
|
39
|
+
* the candidate content to the skill root, record the `activated` event with
|
|
40
|
+
* user/origin + snapshot ref.
|
|
41
|
+
*/
|
|
42
|
+
export declare function activateCandidate(input: ActivateInput): Promise<ActivateResult>;
|
|
43
|
+
export interface RollbackInput {
|
|
44
|
+
directory: string;
|
|
45
|
+
skillSlug: string;
|
|
46
|
+
candidateId: string;
|
|
47
|
+
actor: string;
|
|
48
|
+
}
|
|
49
|
+
export interface RollbackResult {
|
|
50
|
+
rolledBack: boolean;
|
|
51
|
+
reason: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Rollback an activation by restoring the snapshot. Appends a `rolled_back`
|
|
55
|
+
* event — history is NEVER deleted. The restored snapshot becomes the live
|
|
56
|
+
* SKILL.md content atomically.
|
|
57
|
+
*/
|
|
58
|
+
export declare function rollbackCandidate(input: RollbackInput): Promise<RollbackResult>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constrained candidate generation (Workstream B, issue #1822).
|
|
3
|
+
*
|
|
4
|
+
* A candidate is a constrained edit of ONE allowlisted `SKILL.md`. The
|
|
5
|
+
* generator:
|
|
6
|
+
* - is deterministic-first (the deterministic proposal is the seed; LLM free
|
|
7
|
+
* text is supplemental only);
|
|
8
|
+
* - CANNOT see held-out contents/scores/evaluator implementation — its inputs
|
|
9
|
+
* are an explicit allowlist (baseline content, eligible evidence,
|
|
10
|
+
* counterexamples from the rejection ledger). Any attempt to inject a
|
|
11
|
+
* claimed held-out task ID throws LEAKAGE_DETECTED (critic I2);
|
|
12
|
+
* - enforces a trust region (max changed lines/bytes/sections; one target;
|
|
13
|
+
* preserve frontmatter/schema/discoverability/progressive disclosure);
|
|
14
|
+
* - treats historical evidence as untrusted DATA, not instructions (sanitized
|
|
15
|
+
* before injection).
|
|
16
|
+
*/
|
|
17
|
+
import type { SkillOptConfig } from '../../config/schema.js';
|
|
18
|
+
/** Allowlisted generator inputs — anything else is leakage. */
|
|
19
|
+
export interface GeneratorInputs {
|
|
20
|
+
/** Current baseline SKILL.md content. */
|
|
21
|
+
baselineContent: string;
|
|
22
|
+
/** Eligible evidence entries (sanitized, untrusted-as-data). */
|
|
23
|
+
eligibleEvidence: readonly SanitizedEvidence[];
|
|
24
|
+
/** Counterexamples from the rejection ledger (prevents re-drafting rejects). */
|
|
25
|
+
counterexamples: readonly string[];
|
|
26
|
+
/** Edit/token budget for this round. */
|
|
27
|
+
budget: {
|
|
28
|
+
maxChangedLines: number;
|
|
29
|
+
maxChangedBytes: number;
|
|
30
|
+
maxChangedSections: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface SanitizedEvidence {
|
|
34
|
+
id: string;
|
|
35
|
+
triggers: string[];
|
|
36
|
+
requiredActions: string[];
|
|
37
|
+
forbiddenActions: string[];
|
|
38
|
+
confidence: number;
|
|
39
|
+
}
|
|
40
|
+
/** IDs of held-out test tasks the generator must never see. */
|
|
41
|
+
export type ClaimedTestTaskIds = ReadonlySet<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Build the generator's input slice from raw materials. Throws LEAKAGE_DETECTED
|
|
44
|
+
* if any held-out task ID is referenced in evidence (defense-in-depth — the
|
|
45
|
+
* generator should never receive held-out task content, but we also refuse the
|
|
46
|
+
* IDs to prevent indirect leakage through metadata).
|
|
47
|
+
*/
|
|
48
|
+
export declare function buildGeneratorInputs(args: {
|
|
49
|
+
baselineContent: string;
|
|
50
|
+
eligibleEvidence: readonly SanitizedEvidence[];
|
|
51
|
+
counterexamples: readonly string[];
|
|
52
|
+
budget: SkillOptConfig;
|
|
53
|
+
claimedTestTaskIds: ClaimedTestTaskIds;
|
|
54
|
+
}): GeneratorInputs;
|
|
55
|
+
export interface DraftedCandidate {
|
|
56
|
+
content: string;
|
|
57
|
+
diffSummary: {
|
|
58
|
+
changedLines: number;
|
|
59
|
+
changedBytes: number;
|
|
60
|
+
changedSections: number;
|
|
61
|
+
};
|
|
62
|
+
rationale: string;
|
|
63
|
+
risks: string[];
|
|
64
|
+
rollbackSnapshot: string;
|
|
65
|
+
metric: {
|
|
66
|
+
eligibilityScore: number;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** DI seam. The deterministic drafter is the default; LLM supplemental is optional. */
|
|
70
|
+
export declare const _internals: {
|
|
71
|
+
/** Deterministic draft: appends a constrained "## Optimization Notes" section. */
|
|
72
|
+
draftDeterministic: typeof defaultDeterministicDraft;
|
|
73
|
+
};
|
|
74
|
+
declare function defaultDeterministicDraft(inputs: GeneratorInputs): DraftedCandidate;
|
|
75
|
+
/** Enforce the trust region; throws TrustRegionViolation on breach. */
|
|
76
|
+
export declare function enforceTrustRegion(candidate: DraftedCandidate, budget: {
|
|
77
|
+
maxChangedLines: number;
|
|
78
|
+
maxChangedBytes: number;
|
|
79
|
+
maxChangedSections: number;
|
|
80
|
+
}): void;
|
|
81
|
+
/** Equivalent-patch detection: identical content hash → no-op, stop convergence. */
|
|
82
|
+
export declare function isEquivalentPatch(baselineContent: string, candidateContent: string): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Produce a candidate from inputs. Deterministic-first; the LLM delegate (if
|
|
85
|
+
* wired) is supplemental and must stay within the same trust region.
|
|
86
|
+
*/
|
|
87
|
+
export declare function draftCandidate(inputs: GeneratorInputs): DraftedCandidate;
|
|
88
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serial controller (Workstream C, issue #1822).
|
|
3
|
+
*
|
|
4
|
+
* One project run + one target skill at a time, enforced by a cross-process
|
|
5
|
+
* lock with a stale-lock policy (inherited from `file-locks.ts` — 5 min stale
|
|
6
|
+
* TTL). The round flow:
|
|
7
|
+
*
|
|
8
|
+
* acquireRunLock (project-wide) → acquireSkillLock (<slug>)
|
|
9
|
+
* → freeze baseline (contentHash + snapshot)
|
|
10
|
+
* → deterministic seed (Workstream A)
|
|
11
|
+
* → draft constrained candidate (Workstream B)
|
|
12
|
+
* → static smoke → smoke_validated
|
|
13
|
+
* → evaluateCandidateV1 (split:'test') → validation_running
|
|
14
|
+
* → decision.status → accepted_pending_approval | rejected | inconclusive
|
|
15
|
+
* release (reverse order)
|
|
16
|
+
*
|
|
17
|
+
* Caps (enforced): max_rounds (draft/smoke retries before a validation),
|
|
18
|
+
* max_transient_retries (infra-retries before inconclusive), deadband
|
|
19
|
+
* (forwarded to the promotion policy), convergence_non_improvements (K
|
|
20
|
+
* draft/smoke non-progress stops). A completed validation (accept/reject/
|
|
21
|
+
* inconclusive) is terminal — the held-out set is single-use. Not enforced in
|
|
22
|
+
* v1: max_candidates_per_round (one candidate per round), max_tokens_per_round
|
|
23
|
+
* /spend accounting, a wall-clock round timer (max_round_time_ms is forwarded
|
|
24
|
+
* only as a per-task validation timeout), max_rejections and
|
|
25
|
+
* max_inconclusive_rounds (declared in the schema for forward-compatibility
|
|
26
|
+
* with a future multi-validation loop, but unused now that a single run
|
|
27
|
+
* performs at most one validation).
|
|
28
|
+
*
|
|
29
|
+
* Transient infra failures → inconclusive with bounded retry
|
|
30
|
+
* (`max_transient_retries`, default 5) BEFORE counting as a rejection.
|
|
31
|
+
* Non-transient failures → hard stop + `telemetry.loopDetected` + a
|
|
32
|
+
* `NON-TRANSIENT STOP` advisory (does NOT mutate the active skill).
|
|
33
|
+
*
|
|
34
|
+
* A test-set result NEVER auto-generates a round — `claimHeldOutTest`
|
|
35
|
+
* (`split:'test'`) throws `TestAlreadyConsumedError` on reuse. A single `run`
|
|
36
|
+
* therefore performs AT MOST ONE validation; draft/smoke retries are the only
|
|
37
|
+
* looped steps, and a completed validation (accept/reject/inconclusive) stops
|
|
38
|
+
* the loop. A re-test requires a fresh task set, i.e. a new `run`.
|
|
39
|
+
*/
|
|
40
|
+
import type { SkillOptConfig } from '../../config/schema.js';
|
|
41
|
+
import type { EvaluationCandidateV1, EvaluationRunV1, PromotionDecisionV1 } from '../../evaluation/contracts.js';
|
|
42
|
+
import type { EvaluationModelDispatcher } from '../../evaluation/model-dispatcher.js';
|
|
43
|
+
import { evaluateCandidateV1 } from '../../evaluation/public-api.js';
|
|
44
|
+
import { tryAcquireLock } from '../../parallel/file-locks.js';
|
|
45
|
+
import { emit as emitTelemetry } from '../../telemetry.js';
|
|
46
|
+
import { type GeneratorInputs } from './candidates.js';
|
|
47
|
+
import { currentCandidateState } from './lifecycle.js';
|
|
48
|
+
export type Origin = 'command:skill-opt:run' | 'command:skill-opt:plan';
|
|
49
|
+
export interface RunRoundInput {
|
|
50
|
+
directory: string;
|
|
51
|
+
skillSlug: string;
|
|
52
|
+
config: SkillOptConfig;
|
|
53
|
+
/** Caller's session ID (for telemetry). */
|
|
54
|
+
sessionId?: string;
|
|
55
|
+
/** Evaluation dispatcher from CommandContext.evaluationModelDispatcher. */
|
|
56
|
+
dispatcher?: EvaluationModelDispatcher;
|
|
57
|
+
/** Model IDs to evaluate against. */
|
|
58
|
+
models: string[];
|
|
59
|
+
/** Evidence to seed the draft (if absent, the deterministic seed is used). */
|
|
60
|
+
seedEvidence?: GeneratorInputs['eligibleEvidence'];
|
|
61
|
+
/** Held-out task IDs the generator must not see (leakage defense). */
|
|
62
|
+
claimedTestTaskIds?: ReadonlySet<string>;
|
|
63
|
+
/** Test task fixtures for validation (assembled by the command layer). */
|
|
64
|
+
validationTasks: unknown[];
|
|
65
|
+
/**
|
|
66
|
+
* The root directory the evaluation substrate resolves task paths against
|
|
67
|
+
* (instructionPath, environment.path, scorer argv). This is where the
|
|
68
|
+
* command layer materialized the fixture files — NOT the project root.
|
|
69
|
+
* (F1 fix: previously the controller passed input.directory, causing
|
|
70
|
+
* file-not-found on every real validation.)
|
|
71
|
+
*/
|
|
72
|
+
inputRoot?: string;
|
|
73
|
+
/** Baseline + candidate EvaluationCandidateV1 builders are derived from content. */
|
|
74
|
+
baselineModel: string;
|
|
75
|
+
candidateModel: string;
|
|
76
|
+
/** AbortSignal for cooperative cancellation. */
|
|
77
|
+
abortSignal?: AbortSignal;
|
|
78
|
+
/** Origin for lifecycle events. */
|
|
79
|
+
origin: Origin;
|
|
80
|
+
/** Dry-run: if true, plan only, do not execute validation. */
|
|
81
|
+
dryRun?: boolean;
|
|
82
|
+
}
|
|
83
|
+
export interface RunRoundResult {
|
|
84
|
+
candidateId: string;
|
|
85
|
+
decidedState: string;
|
|
86
|
+
decision?: PromotionDecisionV1;
|
|
87
|
+
run?: EvaluationRunV1;
|
|
88
|
+
stopped: boolean;
|
|
89
|
+
stopReason?: string;
|
|
90
|
+
error?: string;
|
|
91
|
+
/** True when a non-transient validation failure hard-stopped this round.
|
|
92
|
+
* The optimization loop must stop immediately (AGENTS.md invariant #9). */
|
|
93
|
+
hardStop?: boolean;
|
|
94
|
+
}
|
|
95
|
+
/** DI seam for lock + telemetry injection (AGENTS.md invariant #7). */
|
|
96
|
+
/**
|
|
97
|
+
* Prepare candidate payloads inside inputRoot and compute substrate-compatible
|
|
98
|
+
* content hashes. Extracted to _internals so tests that mock evaluateCandidateV1
|
|
99
|
+
* can bypass the real filesystem hash computation (which is platform-sensitive).
|
|
100
|
+
*/
|
|
101
|
+
declare function prepareCandidatePayloads(inputRoot: string, candidateId: string, skillSlug: string, baselineModel: string, candidateModel: string, baselineContent: string, candidateContent: string): Promise<{
|
|
102
|
+
baseline: EvaluationCandidateV1;
|
|
103
|
+
candidate: EvaluationCandidateV1;
|
|
104
|
+
}>;
|
|
105
|
+
export declare const _internals: {
|
|
106
|
+
tryAcquireLock: typeof tryAcquireLock;
|
|
107
|
+
emitTelemetry: typeof emitTelemetry;
|
|
108
|
+
evaluateCandidateV1: typeof evaluateCandidateV1;
|
|
109
|
+
prepareCandidatePayloads: typeof prepareCandidatePayloads;
|
|
110
|
+
};
|
|
111
|
+
/** Execute a single optimization round for one skill. */
|
|
112
|
+
export declare function runOptimizationRound(input: RunRoundInput): Promise<RunRoundResult>;
|
|
113
|
+
/** Read convergence state (non-improvement count) for the K-stop check. */
|
|
114
|
+
export declare function readConvergenceState(directory: string): {
|
|
115
|
+
nonImprovements: number;
|
|
116
|
+
};
|
|
117
|
+
/** Update convergence state after a round. */
|
|
118
|
+
export declare function writeConvergenceState(directory: string, nonImprovements: number): void;
|
|
119
|
+
export interface OptimizationLoopResult {
|
|
120
|
+
rounds: RunRoundResult[];
|
|
121
|
+
stopped: boolean;
|
|
122
|
+
stopReason: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Drive optimization rounds until a cap or stop condition is hit (reviewer CR3).
|
|
126
|
+
*
|
|
127
|
+
* IMPORTANT — held-out test set is single-use (final critic FC2): a validation
|
|
128
|
+
* call consumes the `split:'test'` task set (`claimHeldOutTest` throws
|
|
129
|
+
* `TestAlreadyConsumedError` on reuse). Therefore a single `run` performs AT
|
|
130
|
+
* MOST ONE validation; the loop's rounds are draft-and-smoke retries that can
|
|
131
|
+
* loop without consuming the held-out set (equivalent-patch, smoke-fail,
|
|
132
|
+
* draft-fail). Once a round reaches validation (accept/reject/inconclusive),
|
|
133
|
+
* the loop STOPS — a re-validation requires a fresh task set, i.e. a new `run`.
|
|
134
|
+
*
|
|
135
|
+
* Caps + stop conditions (all from `config`):
|
|
136
|
+
* - `max_rounds`: hard cap on draft-and-smoke retries before a validation.
|
|
137
|
+
* - `convergence_non_improvements`: K consecutive non-accept results stops.
|
|
138
|
+
* - any non-transient hard stop, equivalent-patch, lock, or completed
|
|
139
|
+
* validation stops immediately.
|
|
140
|
+
*
|
|
141
|
+
* Each round is a fresh candidate (mintCandidateId inside runOptimizationRound).
|
|
142
|
+
* A test-set result NEVER auto-generates a round — only this explicit loop does.
|
|
143
|
+
*/
|
|
144
|
+
export declare function runOptimizationLoop(input: RunRoundInput): Promise<OptimizationLoopResult>;
|
|
145
|
+
/** Re-export current state helper for the command layer. */
|
|
146
|
+
export { currentCandidateState };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic candidate seed (Workstream A, issue #1822 — #1771 lifecycle closure).
|
|
3
|
+
*
|
|
4
|
+
* The seed is deterministic-first: it composes the existing eligibility
|
|
5
|
+
* functions (`selectCandidateEntries` + `isSkillMaturityEligible` from
|
|
6
|
+
* skill-generator.ts) and sanitizes them into `GeneratorInputs`. LLM free text
|
|
7
|
+
* is supplemental only and is gated off by default (the controller may wire a
|
|
8
|
+
* delegate if `skill_opt.enabled === true` and a delegate is supplied).
|
|
9
|
+
*/
|
|
10
|
+
import type { SanitizedEvidence } from './candidates.js';
|
|
11
|
+
export interface DeterministicSeedInput {
|
|
12
|
+
directory: string;
|
|
13
|
+
/** Override confidence/confirmation floors. */
|
|
14
|
+
minConfidence?: number;
|
|
15
|
+
minConfirmations?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface DeterministicSeedResult {
|
|
18
|
+
evidence: readonly SanitizedEvidence[];
|
|
19
|
+
/** Count of considered entries that were filtered out (diagnostic). */
|
|
20
|
+
filteredOut: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Produce a deterministic seed of eligible evidence for a candidate draft.
|
|
24
|
+
* Mirrors the eligibility logic in `selectCandidateEntries` /
|
|
25
|
+
* `isSkillMaturityEligible` so the optimizer's seed is consistent with the
|
|
26
|
+
* rest of the skill system. LLM free text is supplemental only and not
|
|
27
|
+
* produced here.
|
|
28
|
+
*/
|
|
29
|
+
export declare function deterministicSeed(input: DeterministicSeedInput): Promise<DeterministicSeedResult>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle state machine for the governed skill optimizer (issue #1822).
|
|
3
|
+
*
|
|
4
|
+
* Legal transitions encode the durable lifecycle:
|
|
5
|
+
* discovered → drafted → smoke_validated → validation_running
|
|
6
|
+
* → accepted_pending_approval | rejected | inconclusive
|
|
7
|
+
* accepted_pending_approval → activated | expired | rolled_back
|
|
8
|
+
* inconclusive → drafted (re-entry with a fresh candidate + task set, D8)
|
|
9
|
+
* activated → rolled_back
|
|
10
|
+
* rejected → (terminal)
|
|
11
|
+
* expired → (terminal)
|
|
12
|
+
*
|
|
13
|
+
* `recordTransition` wraps `store.appendEvent` with:
|
|
14
|
+
* - legal-transition enforcement (IllegalTransitionError otherwise);
|
|
15
|
+
* - replay-from-last-complete (restart never reruns a one-shot validation —
|
|
16
|
+
* validation run refs are immutable; a re-validation is a NEW candidate);
|
|
17
|
+
* - replay-after-write verification (inherited from the store).
|
|
18
|
+
*/
|
|
19
|
+
import { computeStateHash, type SkillOptEvent, type SkillOptState } from './store.js';
|
|
20
|
+
export declare class IllegalTransitionError extends Error {
|
|
21
|
+
readonly from: SkillOptState | null;
|
|
22
|
+
readonly to: SkillOptState;
|
|
23
|
+
constructor(from: SkillOptState | null, to: SkillOptState);
|
|
24
|
+
}
|
|
25
|
+
export declare function isLegalTransition(from: SkillOptState | null, to: SkillOptState): boolean;
|
|
26
|
+
export declare function assertTransition(from: SkillOptState | null, to: SkillOptState): void;
|
|
27
|
+
export interface RecordTransitionInput {
|
|
28
|
+
directory: string;
|
|
29
|
+
skillSlug: string;
|
|
30
|
+
candidateId: string;
|
|
31
|
+
toState: SkillOptState;
|
|
32
|
+
eventType: string;
|
|
33
|
+
actor: string;
|
|
34
|
+
origin: string;
|
|
35
|
+
reason: string;
|
|
36
|
+
evidenceRefs?: string[];
|
|
37
|
+
contentHashBefore?: string | null;
|
|
38
|
+
contentHashAfter?: string | null;
|
|
39
|
+
payload?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
export interface RecordTransitionResult {
|
|
42
|
+
event: SkillOptEvent;
|
|
43
|
+
fromState: SkillOptState | null;
|
|
44
|
+
replayTruncated: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Record a lifecycle transition.
|
|
48
|
+
*
|
|
49
|
+
* 1. Replays the candidate ledger to derive the current `fromState` (restart-
|
|
50
|
+
* safe). If the ledger tail is corrupt, quarantines the suffix and uses the
|
|
51
|
+
* last complete event's `toState` as `fromState`.
|
|
52
|
+
* 2. Asserts the `from -> to` transition is legal.
|
|
53
|
+
* 3. Appends the event (hash-chained, fsync+rename, replay-after-write verify).
|
|
54
|
+
* 4. Refreshes the derived `state.json` projection.
|
|
55
|
+
*
|
|
56
|
+
* Restart rule: a one-shot validation is NEVER rerun. If `toState` is
|
|
57
|
+
* `validation_running` and the candidate already has a completed validation
|
|
58
|
+
* event, this throws — the caller must mint a new candidate ID for a re-test.
|
|
59
|
+
*/
|
|
60
|
+
export declare function recordTransition(input: RecordTransitionInput): Promise<RecordTransitionResult>;
|
|
61
|
+
/** Convenience: current candidate state via replay (re-derived each call). */
|
|
62
|
+
export declare function currentCandidateState(directory: string, skillSlug: string, candidateId: string): {
|
|
63
|
+
state: SkillOptState | null;
|
|
64
|
+
lastEvent: SkillOptEvent | null;
|
|
65
|
+
truncated: boolean;
|
|
66
|
+
};
|
|
67
|
+
/** Whether a candidate has reached a terminal state. */
|
|
68
|
+
export declare function isTerminal(state: SkillOptState | null): boolean;
|
|
69
|
+
/** Re-export for callers that need the hash helper. */
|
|
70
|
+
export { computeStateHash };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Distinct `promoted_external` staleness policy (Workstream A, issue #1822).
|
|
3
|
+
*
|
|
4
|
+
* Today `curator.ts:1791` SKIPS promoted-external skills entirely during the
|
|
5
|
+
* staleness sweep. That means a promoted-external skill whose source knowledge
|
|
6
|
+
* changed (or which became unsupported) is never reconciled. This module
|
|
7
|
+
* provides the distinct policy the curator now consults:
|
|
8
|
+
*
|
|
9
|
+
* - source-knowledge-changed → regenerate (advisory: the curator LOGS source
|
|
10
|
+
* drift; it does not auto-regenerate promoted-external skills — the user
|
|
11
|
+
* re-runs external discovery);
|
|
12
|
+
* - wall-clock retirement (configurable) using the REAL usage signal (#1770),
|
|
13
|
+
* with minimum-age and support safeguards, and a REVERSIBLE archive.
|
|
14
|
+
*
|
|
15
|
+
* The decision is advisory: the curator routes `retire` to `retireSkill`
|
|
16
|
+
* (reversible / marked); `regenerate` is log-only advisory (the user decides
|
|
17
|
+
* whether to re-run discovery/regeneration).
|
|
18
|
+
*/
|
|
19
|
+
export type PromotedExternalStalenessDecision = {
|
|
20
|
+
action: 'current';
|
|
21
|
+
reason: string;
|
|
22
|
+
} | {
|
|
23
|
+
action: 'regenerate';
|
|
24
|
+
reason: string;
|
|
25
|
+
sourceKnowledgeIds: string[];
|
|
26
|
+
} | {
|
|
27
|
+
action: 'retire';
|
|
28
|
+
reason: string;
|
|
29
|
+
};
|
|
30
|
+
export interface PromotedExternalStalenessInput {
|
|
31
|
+
directory: string;
|
|
32
|
+
skillSlug: string;
|
|
33
|
+
/** Frontmatter `skill_origin` — must be `promoted_external`. */
|
|
34
|
+
origin?: string;
|
|
35
|
+
/** Frontmatter `source_knowledge_ids` (the knowledge entries this skill came from). */
|
|
36
|
+
sourceKnowledgeIds?: string[];
|
|
37
|
+
/** Real usage counts (from the #1770 skill-usage signal). */
|
|
38
|
+
usage: {
|
|
39
|
+
appliedExplicitCount: number;
|
|
40
|
+
ignoredCount: number;
|
|
41
|
+
violatedCount: number;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Caller signals that the source knowledge entries have changed since the
|
|
45
|
+
* skill was promoted (F5 fix: makes the 'regenerate' action reachable).
|
|
46
|
+
* The curator compares the source knowledge IDs' current content/revision
|
|
47
|
+
* against what the skill was compiled from.
|
|
48
|
+
*/
|
|
49
|
+
sourceChanged?: boolean;
|
|
50
|
+
/** Days since the skill was last regenerated / promoted. */
|
|
51
|
+
ageDays: number;
|
|
52
|
+
/** Configurable floor; defaults to 60. */
|
|
53
|
+
retirementMinAgeDays: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parse `source_knowledge_ids`, `skill_origin`, and `promoted_at` from
|
|
57
|
+
* SKILL.md YAML frontmatter CONTENT. Pure (no I/O) so it is directly testable.
|
|
58
|
+
* Returns `{}` if frontmatter is absent or unparseable.
|
|
59
|
+
*/
|
|
60
|
+
export declare function parsePromotedExternalFrontmatter(content: string): {
|
|
61
|
+
origin?: string;
|
|
62
|
+
sourceKnowledgeIds?: string[];
|
|
63
|
+
promotedAt?: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Frontmatter helper: read a SKILL.md from disk and parse its frontmatter.
|
|
67
|
+
* Returns `{}` if absent or unparseable.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readPromotedExternalFrontmatter(skillPath: string): {
|
|
70
|
+
origin?: string;
|
|
71
|
+
sourceKnowledgeIds?: string[];
|
|
72
|
+
promotedAt?: string;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Evaluate staleness for a promoted-external skill.
|
|
76
|
+
*
|
|
77
|
+
* Rules (in priority order):
|
|
78
|
+
* 1. If source knowledge IDs are present but no longer exist in the knowledge
|
|
79
|
+
* store → `retire` (the source has been deleted; keeping a dangling skill
|
|
80
|
+
* is worse than retiring).
|
|
81
|
+
* 2. If the skill has been ignored/violated far more than applied AND has
|
|
82
|
+
* passed the minimum age floor → `retire` (real negative usage signal).
|
|
83
|
+
* 3. If source knowledge IDs are present and still exist but their content
|
|
84
|
+
* hash changed since promotion (caller passes `sourceChanged: true`) →
|
|
85
|
+
* `regenerate`.
|
|
86
|
+
* 4. Otherwise → `current`.
|
|
87
|
+
*
|
|
88
|
+
* `ageDays < retirementMinAgeDays` blocks retirement (minimum-age safeguard).
|
|
89
|
+
* Retirement is REVERSIBLE: `retireSkill` records a `retired.marker` and the
|
|
90
|
+
* skill can be restored (the curator's existing restore path).
|
|
91
|
+
*/
|
|
92
|
+
export declare function evaluatePromotedExternalStaleness(input: PromotedExternalStalenessInput): PromotedExternalStalenessDecision;
|
|
93
|
+
/**
|
|
94
|
+
* Convenience: build the input for the curator from a skill's frontmatter + the
|
|
95
|
+
* skill-usage log. The curator already has the usage signal; this just reads
|
|
96
|
+
* the frontmatter pieces.
|
|
97
|
+
*/
|
|
98
|
+
export declare function buildPromotedExternalInputFromSkill(directory: string, skillSlug: string, usage: PromotedExternalStalenessInput['usage'], ageDays: number, retirementMinAgeDays: number): PromotedExternalStalenessInput | null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wall-clock retirement gate (Workstream A, issue #1822).
|
|
3
|
+
*
|
|
4
|
+
* Uses the REAL usage signal (#1770) plus minimum-age and support safeguards.
|
|
5
|
+
* Retirement is always REVERSIBLE: `retireSkill` (skill-generator.ts) records a
|
|
6
|
+
* `retired.marker` and the skill can be restored via the existing restore path.
|
|
7
|
+
*
|
|
8
|
+
* Also exposes the explicit `outcomeSignal === 0` (zero-evidence) boundary
|
|
9
|
+
* classification used by eligibility — distinct from the existing
|
|
10
|
+
* strongOutcomes test (`tests/unit/services/skill-generator.test.ts:731-758`),
|
|
11
|
+
* which covers `outcomeSignal === 0` WITH `strongOutcomes=true`. This module
|
|
12
|
+
* covers the zero-evidence branch (no positives, no negatives) explicitly.
|
|
13
|
+
*/
|
|
14
|
+
import type { RetrievalOutcome } from '../../hooks/knowledge-types.js';
|
|
15
|
+
export type OutcomeSignalClass = 'positive' | 'negative' | 'zero_evidence';
|
|
16
|
+
/**
|
|
17
|
+
* Classify an outcome signal. The zero-evidence branch (`=== 0` with no
|
|
18
|
+
* outcomes at all) is distinct from "balanced positives and negatives" —
|
|
19
|
+
* `computeOutcomeSignal` returns 0 for BOTH cases, but the eligibility gate
|
|
20
|
+
* treats zero as "neither boost nor penalize" only when there is genuinely no
|
|
21
|
+
* evidence. Callers that want the strict zero-evidence classification should
|
|
22
|
+
* pass the raw outcome counts.
|
|
23
|
+
*/
|
|
24
|
+
export declare function classifyOutcomeSignal(outcomes: RetrievalOutcome | undefined): {
|
|
25
|
+
signal: number;
|
|
26
|
+
classification: OutcomeSignalClass;
|
|
27
|
+
};
|
|
28
|
+
export interface RetirementEvaluation {
|
|
29
|
+
retire: boolean;
|
|
30
|
+
reason: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Evaluate whether a skill should be retired on the wall-clock schedule.
|
|
34
|
+
*
|
|
35
|
+
* Safeguards (issue #1822 Workstream A):
|
|
36
|
+
* - minimum age: `ageDays >= minAgeDays` (floor, not a trigger);
|
|
37
|
+
* - real usage: a decisive negative signal is required (#1770);
|
|
38
|
+
* - support: a skill that is still occasionally applied is NOT retired even
|
|
39
|
+
* if old (`applied > 0` blocks retirement unless the negative ratio is
|
|
40
|
+
* extreme).
|
|
41
|
+
*
|
|
42
|
+
* Returns `{ retire: false }` for any skill failing the safeguards. Retirement
|
|
43
|
+
* is reversible (the caller uses `retireSkill`, which only marks).
|
|
44
|
+
*/
|
|
45
|
+
export declare function evaluateRetirement(args: {
|
|
46
|
+
usage: {
|
|
47
|
+
appliedExplicitCount: number;
|
|
48
|
+
ignoredCount: number;
|
|
49
|
+
violatedCount: number;
|
|
50
|
+
failedAfterShownCount: number;
|
|
51
|
+
};
|
|
52
|
+
ageDays: number;
|
|
53
|
+
minAgeDays: number;
|
|
54
|
+
}): RetirementEvaluation;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill-eval task builder (issue #1822 — D1, critic C1/I4).
|
|
3
|
+
*
|
|
4
|
+
* The substrate's `builtin` scorer parses model output as `{caught:boolean}`
|
|
5
|
+
* JSON and ignores `argv` (`runner.ts:144-162`); only `kind:'project'` scorers
|
|
6
|
+
* consume `argv` (`runner.ts:175-228`). So the optimizer's skill-eval tasks
|
|
7
|
+
* use `kind:'project'` scorers that invoke the SHARED `scoreSkillPhrases`
|
|
8
|
+
* function (factored out of skill-evaluator.ts) via a small wrapper script.
|
|
9
|
+
*
|
|
10
|
+
* Rather than ship static fixture manifests with pre-computed `contentHash`
|
|
11
|
+
* values (fragile — any edit invalidates the hash), this module BUILDS valid
|
|
12
|
+
* `EvaluationTaskV1` objects at runtime and materializes their fixture files
|
|
13
|
+
* into a fresh `inputRoot`. The content hash is computed from the materialized
|
|
14
|
+
* files, guaranteeing consistency.
|
|
15
|
+
*/
|
|
16
|
+
import type { EvaluationTaskV1 } from '../../evaluation/contracts.js';
|
|
17
|
+
/** A skill-eval case expressed as required/forbidden phrases (the gate model). */
|
|
18
|
+
export interface SkillEvalTaskSpec {
|
|
19
|
+
id: string;
|
|
20
|
+
category: string;
|
|
21
|
+
protected?: boolean;
|
|
22
|
+
instruction: string;
|
|
23
|
+
requiredPhrases: string[];
|
|
24
|
+
forbiddenPhrases: string[];
|
|
25
|
+
/** Environment fixture body the candidate is evaluated against. */
|
|
26
|
+
environmentBody: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Default skill-eval task set — a minimal, curated set of phrase-based checks
|
|
30
|
+
* a generated/optimized SKILL.md should satisfy. These are intentionally
|
|
31
|
+
* generic (they check that a skill declares its trigger, required actions, and
|
|
32
|
+
* avoids forbidden shortcuts) so they apply across skill slugs. Project-specific
|
|
33
|
+
* eval cases live under `.swarm/skills/evals/<slug>/` (the existing gate).
|
|
34
|
+
*/
|
|
35
|
+
export declare const DEFAULT_SKILL_EVAL_TASKS: readonly SkillEvalTaskSpec[];
|
|
36
|
+
/**
|
|
37
|
+
* Materialize a set of skill-eval task specs into a fresh `inputRoot` and
|
|
38
|
+
* return valid `EvaluationTaskV1[]` (with computed contentHashes). The scorer
|
|
39
|
+
* points at the bundled `score-skill-eval.cjs` wrapper, which reads the
|
|
40
|
+
* candidate content + phrase spec via the `SWARM_EVAL_*` env / artifact dir.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildSkillEvalTasks(args: {
|
|
43
|
+
inputRoot: string;
|
|
44
|
+
tasks?: readonly SkillEvalTaskSpec[];
|
|
45
|
+
/** Repo-relative path to the scorer wrapper (resolved against inputRoot). */
|
|
46
|
+
scorerRelPath: string;
|
|
47
|
+
}): EvaluationTaskV1[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill smoke validator — the `smoke_validated` transition (issue #1822).
|
|
3
|
+
*
|
|
4
|
+
* Composes existing primitives rather than duplicating them:
|
|
5
|
+
* - `validateSkillPath` (path containment — knowledge-validator.ts);
|
|
6
|
+
* - symlink/reparse denial (deny escape; mirrors bundled-skills.ts pattern);
|
|
7
|
+
* - frontmatter schema check (YAML parse + required keys);
|
|
8
|
+
* - the phrase-eval gate (`evaluateSkillChange` / `isRejectedSkillContent`
|
|
9
|
+
* from skill-evaluator.ts) — refuses content the rejection ledger already
|
|
10
|
+
* rejected;
|
|
11
|
+
* - a bounded subprocess check via `spawnAsync` if the skill declares a
|
|
12
|
+
* check command (cwd explicit, stdin ignored, timeout, kill, 512KB cap).
|
|
13
|
+
*/
|
|
14
|
+
import { validateSkillPath } from '../../hooks/knowledge-validator.js';
|
|
15
|
+
import { spawnAsync } from '../../hooks/spawn-helper.js';
|
|
16
|
+
import { evaluateSkillChange, isRejectedSkillContent } from '../skill-evaluator.js';
|
|
17
|
+
export interface SmokeInput {
|
|
18
|
+
directory: string;
|
|
19
|
+
skillSlug: string;
|
|
20
|
+
/** Candidate SKILL.md content (not yet written to the skill root). */
|
|
21
|
+
candidateContent: string;
|
|
22
|
+
/** Incumbent SKILL.md content (current). Empty string if no incumbent. */
|
|
23
|
+
incumbentContent: string;
|
|
24
|
+
/** Optional check command the skill declares (e.g. `["bun", "test"]`). */
|
|
25
|
+
checkCommand?: string[];
|
|
26
|
+
/** Timeout for the optional check command. */
|
|
27
|
+
checkTimeoutMs?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface SmokeResult {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
verdict: 'COMPLIANT' | 'VIOLATED';
|
|
32
|
+
notes: string[];
|
|
33
|
+
}
|
|
34
|
+
export declare const _internals: {
|
|
35
|
+
validateSkillPath: typeof validateSkillPath;
|
|
36
|
+
isSymbolicLink: (p: string) => boolean;
|
|
37
|
+
escapedRoot: (root: string, target: string) => boolean;
|
|
38
|
+
realpath: (p: string) => string;
|
|
39
|
+
evaluateSkillChange: typeof evaluateSkillChange;
|
|
40
|
+
isRejectedSkillContent: typeof isRejectedSkillContent;
|
|
41
|
+
spawnAsync: typeof spawnAsync;
|
|
42
|
+
};
|
|
43
|
+
/** Validate a candidate skill before the validation-running transition. */
|
|
44
|
+
export declare function validateSkillSmoke(input: SmokeInput): Promise<SmokeResult>;
|
|
45
|
+
/** Read the incumbent SKILL.md content, or return empty string if absent. */
|
|
46
|
+
export declare function readIncumbentContent(directory: string, skillSlug: string): string;
|