@agntk/agent-harness 0.1.4 → 0.1.6
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/README.md +104 -43
- package/dist/{analytics-RPT73WNM.js → analytics-L24W3B7U.js} +1 -2
- package/dist/auto-processor-QIRUOGEI.js +12 -0
- package/dist/{chunk-UPLBF4RZ.js → chunk-2UVWCTAY.js} +2 -2
- package/dist/{chunk-CSL3ERUI.js → chunk-4P6TRFPZ.js} +3 -3
- package/dist/chunk-4P6TRFPZ.js.map +1 -0
- package/dist/{chunk-4CWAGBNS.js → chunk-4TQQZILG.js} +73 -3
- package/dist/chunk-4TQQZILG.js.map +1 -0
- package/dist/{chunk-DA7IKHC4.js → chunk-5CO5JTYT.js} +2 -2
- package/dist/chunk-5CO5JTYT.js.map +1 -0
- package/dist/{chunk-A7BJPQQ6.js → chunk-5O5OGOOQ.js} +2 -2
- package/dist/{chunk-UWQTZMNI.js → chunk-7GZ4D6V6.js} +2 -2
- package/dist/{chunk-FLZU44SV.js → chunk-AN6Y4MDD.js} +6 -6
- package/dist/{chunk-4FDUOGSZ.js → chunk-D7AWV24Z.js} +3 -3
- package/dist/{chunk-CHJ5GNZC.js → chunk-EC42HQQH.js} +2 -2
- package/dist/{chunk-YIJY5DBV.js → chunk-KLYMGWQJ.js} +4 -4
- package/dist/chunk-KLYMGWQJ.js.map +1 -0
- package/dist/{chunk-YUFNYN2H.js → chunk-M62KLIEK.js} +4 -4
- package/dist/chunk-M62KLIEK.js.map +1 -0
- package/dist/{chunk-GJNNR2RA.js → chunk-M6PDMK2O.js} +3 -3
- package/dist/{chunk-M7NXUK55.js → chunk-NVC2WY4K.js} +2 -2
- package/dist/{chunk-XTBKL5BI.js → chunk-PMFAYKBD.js} +2 -2
- package/dist/chunk-PMFAYKBD.js.map +1 -0
- package/dist/{chunk-274RV3YO.js → chunk-QMOIVORH.js} +3 -3
- package/dist/chunk-QMOIVORH.js.map +1 -0
- package/dist/{chunk-RY3ZFII7.js → chunk-SEHAQTBO.js} +6 -6
- package/dist/{chunk-MPZ3BPUI.js → chunk-UMXPOYZR.js} +4 -4
- package/dist/{chunk-W4T7PGI2.js → chunk-UXCHAS3Z.js} +4 -4
- package/dist/cli/index.js +153 -167
- package/dist/cli/index.js.map +1 -1
- package/dist/config-PYSS3QY6.js +12 -0
- package/dist/context-loader-RSXXFW5R.js +12 -0
- package/dist/{conversation-QDEIDQPH.js → conversation-TBTFIJVU.js} +6 -7
- package/dist/{cost-tracker-RS3W7SVY.js → cost-tracker-NZRZEHVA.js} +1 -2
- package/dist/{delegate-VJCJLYEK.js → delegate-3KJAL4NZ.js} +7 -8
- package/dist/{emotional-state-VQVRA6ED.js → emotional-state-IN4ZUL2Q.js} +1 -2
- package/dist/{emotional-state-VQVRA6ED.js.map → emotional-state-IN4ZUL2Q.js.map} +1 -1
- package/dist/{env-discovery-2BLVMAIM.js → env-discovery-PXBRE5FX.js} +1 -2
- package/dist/{env-discovery-2BLVMAIM.js.map → env-discovery-PXBRE5FX.js.map} +1 -1
- package/dist/{export-6GCYHEHQ.js → export-GYLWROMB.js} +3 -4
- package/dist/{export-6GCYHEHQ.js.map → export-GYLWROMB.js.map} +1 -1
- package/dist/graph-LEEO37L3.js +13 -0
- package/dist/{harness-WE4SLCML.js → harness-R5FKRICG.js} +8 -9
- package/dist/{health-NZ6WNIMV.js → health-HL2JYHIY.js} +1 -2
- package/dist/indexer-L5UC6J2V.js +15 -0
- package/dist/{instinct-learner-SRM72DHF.js → instinct-learner-QGAMIS3X.js} +5 -6
- package/dist/{intake-4M3HNU43.js → intake-SVJKFHTL.js} +5 -6
- package/dist/{intelligence-HJOCA4SJ.js → intelligence-XPV3MC5U.js} +10 -13
- package/dist/intelligence-XPV3MC5U.js.map +1 -0
- package/dist/{journal-WANJL3MI.js → journal-ITUMKT6U.js} +5 -6
- package/dist/{loader-C3TKIKZR.js → loader-27PLDCOJ.js} +3 -4
- package/dist/{mcp-WTQJJZAO.js → mcp-JSIUJJZV.js} +1 -2
- package/dist/{mcp-discovery-WPAQFL6S.js → mcp-discovery-DG3RQYLM.js} +1 -2
- package/dist/{mcp-discovery-WPAQFL6S.js.map → mcp-discovery-DG3RQYLM.js.map} +1 -1
- package/dist/{mcp-installer-6O2XXD3V.js → mcp-installer-X2TJ2S2G.js} +3 -4
- package/dist/{mcp-installer-6O2XXD3V.js.map → mcp-installer-X2TJ2S2G.js.map} +1 -1
- package/dist/{metrics-KXGNFAAB.js → metrics-2MNINXNQ.js} +1 -2
- package/dist/{primitive-registry-I6VTIR4W.js → primitive-registry-ZMGGXSO5.js} +3 -4
- package/dist/{primitive-registry-I6VTIR4W.js.map → primitive-registry-ZMGGXSO5.js.map} +1 -1
- package/dist/{project-discovery-C4UMD7JI.js → project-discovery-FQLAZKEM.js} +1 -2
- package/dist/project-discovery-FQLAZKEM.js.map +1 -0
- package/dist/{provider-SXPQZ74H.js → provider-HQY6SPZI.js} +1 -2
- package/dist/{rate-limiter-RLRVM325.js → rate-limiter-PH5DCVU4.js} +1 -2
- package/dist/{rule-engine-YGQ3RYZM.js → rule-engine-DM26S77N.js} +3 -4
- package/dist/{rule-engine-YGQ3RYZM.js.map → rule-engine-DM26S77N.js.map} +1 -1
- package/dist/{scaffold-A3VRRCBV.js → scaffold-2F36YVW6.js} +5 -6
- package/dist/{scaffold-A3VRRCBV.js.map → scaffold-2F36YVW6.js.map} +1 -1
- package/dist/{scheduler-XHHIVHRI.js → scheduler-Q7GB2KCW.js} +11 -12
- package/dist/{scheduler-XHHIVHRI.js.map → scheduler-Q7GB2KCW.js.map} +1 -1
- package/dist/{search-V3W5JMJG.js → search-6Y6NCOLQ.js} +3 -4
- package/dist/search-6Y6NCOLQ.js.map +1 -0
- package/dist/{semantic-search-2DTOO5UX.js → semantic-search-FN6FZIXI.js} +3 -4
- package/dist/semantic-search-FN6FZIXI.js.map +1 -0
- package/dist/{serve-DTQ3HENY.js → serve-MXRTP2HE.js} +10 -11
- package/dist/serve-MXRTP2HE.js.map +1 -0
- package/dist/{sessions-CZGVXKQE.js → sessions-G6SZZXWS.js} +1 -2
- package/dist/{sources-RW5DT56F.js → sources-7LDYO5GK.js} +1 -2
- package/dist/{starter-packs-76YUVHEU.js → starter-packs-OR7NI5NA.js} +1 -2
- package/dist/{starter-packs-76YUVHEU.js.map → starter-packs-OR7NI5NA.js.map} +1 -1
- package/dist/{state-GMXILIHW.js → state-25IQEC5C.js} +1 -2
- package/dist/{state-merge-NKO5FRBA.js → state-merge-E333OEIQ.js} +1 -2
- package/dist/{state-merge-NKO5FRBA.js.map → state-merge-E333OEIQ.js.map} +1 -1
- package/dist/{telemetry-UC6PBXC7.js → telemetry-RS2JZUZP.js} +4 -5
- package/dist/{tool-executor-MJ7IG7PQ.js → tool-executor-6I5PHQDY.js} +5 -6
- package/dist/{tools-DZ4KETET.js → tools-NDFJNVHK.js} +4 -5
- package/dist/{types-EW7AIB3R.js → types-NPJZAI72.js} +2 -3
- package/dist/{universal-installer-AAXXYM5A.js → universal-installer-LCAZHFZR.js} +91 -7
- package/dist/universal-installer-LCAZHFZR.js.map +1 -0
- package/dist/validator-LM7RZWSH.js +21 -0
- package/dist/{verification-gate-FYXUX6LH.js → verification-gate-2O6DF2B7.js} +3 -4
- package/dist/verification-gate-2O6DF2B7.js.map +1 -0
- package/dist/{versioning-Z3XNE2Q2.js → versioning-WEGF6KJG.js} +1 -2
- package/dist/versioning-WEGF6KJG.js.map +1 -0
- package/dist/{watcher-ISJC7YKL.js → watcher-GZWQSWZ6.js} +5 -6
- package/dist/{watcher-ISJC7YKL.js.map → watcher-GZWQSWZ6.js.map} +1 -1
- package/dist/{web-server-DD7ZOP46.js → web-server-2Y4CHD2W.js} +8 -9
- package/package.json +1 -9
- package/dist/agent-framework-K4GUIICH.js +0 -344
- package/dist/agent-framework-K4GUIICH.js.map +0 -1
- package/dist/auto-processor-OLE45UI3.js +0 -13
- package/dist/chunk-274RV3YO.js.map +0 -1
- package/dist/chunk-4CWAGBNS.js.map +0 -1
- package/dist/chunk-CSL3ERUI.js.map +0 -1
- package/dist/chunk-DA7IKHC4.js.map +0 -1
- package/dist/chunk-DGUM43GV.js +0 -11
- package/dist/chunk-FD55B3IO.js +0 -204
- package/dist/chunk-FD55B3IO.js.map +0 -1
- package/dist/chunk-GUJTBGVS.js +0 -2212
- package/dist/chunk-GUJTBGVS.js.map +0 -1
- package/dist/chunk-KFX54TQM.js +0 -165
- package/dist/chunk-KFX54TQM.js.map +0 -1
- package/dist/chunk-XTBKL5BI.js.map +0 -1
- package/dist/chunk-YIJY5DBV.js.map +0 -1
- package/dist/chunk-YUFNYN2H.js.map +0 -1
- package/dist/chunk-ZZJOFKAT.js +0 -13
- package/dist/config-WVMRUOCA.js +0 -13
- package/dist/context-loader-3ORBPMHJ.js +0 -13
- package/dist/graph-YUIPOSOO.js +0 -14
- package/dist/harness-LCHA3DWP.js +0 -10
- package/dist/index.d.ts +0 -3612
- package/dist/index.js +0 -13713
- package/dist/index.js.map +0 -1
- package/dist/indexer-LONANRRM.js +0 -16
- package/dist/intelligence-HJOCA4SJ.js.map +0 -1
- package/dist/project-discovery-C4UMD7JI.js.map +0 -1
- package/dist/provider-LQHQX7Z7.js +0 -26
- package/dist/search-V3W5JMJG.js.map +0 -1
- package/dist/semantic-search-2DTOO5UX.js.map +0 -1
- package/dist/serve-DTQ3HENY.js.map +0 -1
- package/dist/tools-DZ4KETET.js.map +0 -1
- package/dist/types-EW7AIB3R.js.map +0 -1
- package/dist/types-WGDLSPO6.js +0 -16
- package/dist/types-WGDLSPO6.js.map +0 -1
- package/dist/universal-installer-AAXXYM5A.js.map +0 -1
- package/dist/validator-7WXMDIHH.js +0 -22
- package/dist/validator-7WXMDIHH.js.map +0 -1
- package/dist/verification-gate-FYXUX6LH.js.map +0 -1
- package/dist/versioning-Z3XNE2Q2.js.map +0 -1
- package/dist/web-server-DD7ZOP46.js.map +0 -1
- /package/dist/{analytics-RPT73WNM.js.map → analytics-L24W3B7U.js.map} +0 -0
- /package/dist/{auto-processor-OLE45UI3.js.map → auto-processor-QIRUOGEI.js.map} +0 -0
- /package/dist/{chunk-UPLBF4RZ.js.map → chunk-2UVWCTAY.js.map} +0 -0
- /package/dist/{chunk-A7BJPQQ6.js.map → chunk-5O5OGOOQ.js.map} +0 -0
- /package/dist/{chunk-UWQTZMNI.js.map → chunk-7GZ4D6V6.js.map} +0 -0
- /package/dist/{chunk-FLZU44SV.js.map → chunk-AN6Y4MDD.js.map} +0 -0
- /package/dist/{chunk-4FDUOGSZ.js.map → chunk-D7AWV24Z.js.map} +0 -0
- /package/dist/{chunk-CHJ5GNZC.js.map → chunk-EC42HQQH.js.map} +0 -0
- /package/dist/{chunk-GJNNR2RA.js.map → chunk-M6PDMK2O.js.map} +0 -0
- /package/dist/{chunk-M7NXUK55.js.map → chunk-NVC2WY4K.js.map} +0 -0
- /package/dist/{chunk-RY3ZFII7.js.map → chunk-SEHAQTBO.js.map} +0 -0
- /package/dist/{chunk-MPZ3BPUI.js.map → chunk-UMXPOYZR.js.map} +0 -0
- /package/dist/{chunk-W4T7PGI2.js.map → chunk-UXCHAS3Z.js.map} +0 -0
- /package/dist/{chunk-DGUM43GV.js.map → config-PYSS3QY6.js.map} +0 -0
- /package/dist/{chunk-ZZJOFKAT.js.map → context-loader-RSXXFW5R.js.map} +0 -0
- /package/dist/{config-WVMRUOCA.js.map → conversation-TBTFIJVU.js.map} +0 -0
- /package/dist/{context-loader-3ORBPMHJ.js.map → cost-tracker-NZRZEHVA.js.map} +0 -0
- /package/dist/{conversation-QDEIDQPH.js.map → delegate-3KJAL4NZ.js.map} +0 -0
- /package/dist/{cost-tracker-RS3W7SVY.js.map → graph-LEEO37L3.js.map} +0 -0
- /package/dist/{delegate-VJCJLYEK.js.map → harness-R5FKRICG.js.map} +0 -0
- /package/dist/{graph-YUIPOSOO.js.map → health-HL2JYHIY.js.map} +0 -0
- /package/dist/{harness-LCHA3DWP.js.map → indexer-L5UC6J2V.js.map} +0 -0
- /package/dist/{harness-WE4SLCML.js.map → instinct-learner-QGAMIS3X.js.map} +0 -0
- /package/dist/{health-NZ6WNIMV.js.map → intake-SVJKFHTL.js.map} +0 -0
- /package/dist/{indexer-LONANRRM.js.map → journal-ITUMKT6U.js.map} +0 -0
- /package/dist/{instinct-learner-SRM72DHF.js.map → loader-27PLDCOJ.js.map} +0 -0
- /package/dist/{intake-4M3HNU43.js.map → mcp-JSIUJJZV.js.map} +0 -0
- /package/dist/{journal-WANJL3MI.js.map → metrics-2MNINXNQ.js.map} +0 -0
- /package/dist/{loader-C3TKIKZR.js.map → provider-HQY6SPZI.js.map} +0 -0
- /package/dist/{mcp-WTQJJZAO.js.map → rate-limiter-PH5DCVU4.js.map} +0 -0
- /package/dist/{metrics-KXGNFAAB.js.map → sessions-G6SZZXWS.js.map} +0 -0
- /package/dist/{provider-LQHQX7Z7.js.map → sources-7LDYO5GK.js.map} +0 -0
- /package/dist/{provider-SXPQZ74H.js.map → state-25IQEC5C.js.map} +0 -0
- /package/dist/{rate-limiter-RLRVM325.js.map → telemetry-RS2JZUZP.js.map} +0 -0
- /package/dist/{sessions-CZGVXKQE.js.map → tool-executor-6I5PHQDY.js.map} +0 -0
- /package/dist/{sources-RW5DT56F.js.map → tools-NDFJNVHK.js.map} +0 -0
- /package/dist/{state-GMXILIHW.js.map → types-NPJZAI72.js.map} +0 -0
- /package/dist/{telemetry-UC6PBXC7.js.map → validator-LM7RZWSH.js.map} +0 -0
- /package/dist/{tool-executor-MJ7IG7PQ.js.map → web-server-2Y4CHD2W.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/state-merge.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport type { AgentState } from '../core/types.js';\nimport { loadState, saveState } from './state.js';\nimport { withFileLockSync } from './file-lock.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport type StateOwner = 'human' | 'agent' | 'infrastructure';\n\n/** Tracks which entity last modified each field of the state. */\nexport interface StateOwnership {\n mode: StateOwner;\n goals: StateOwner;\n active_workflows: StateOwner;\n last_interaction: StateOwner;\n unfinished_business: StateOwner;\n}\n\n/** A state change with ownership metadata. */\nexport interface OwnedStateChange {\n /** Who is making this change */\n author: StateOwner;\n /** Partial state to merge — only provided fields are updated */\n changes: Partial<AgentState>;\n /** Timestamp of the change (ISO string) */\n timestamp?: string;\n}\n\n/** Strategy for resolving conflicting state changes. */\nexport type MergeStrategy = 'human-wins' | 'agent-wins' | 'latest-wins' | 'union';\n\nexport interface MergeResult {\n /** The merged state */\n state: AgentState;\n /** Updated ownership */\n ownership: StateOwnership;\n /** Fields that had conflicts */\n conflicts: StateConflict[];\n /** Whether any conflicts were resolved */\n hadConflicts: boolean;\n}\n\nexport interface StateConflict {\n field: keyof AgentState;\n humanValue: unknown;\n agentValue: unknown;\n resolvedTo: StateOwner;\n resolvedValue: unknown;\n}\n\n// ─── Ownership Tracking ─────────────────────────────────────────────────────\n\nconst DEFAULT_OWNERSHIP: StateOwnership = {\n mode: 'agent',\n goals: 'human',\n active_workflows: 'agent',\n last_interaction: 'infrastructure',\n unfinished_business: 'agent',\n};\n\nconst OWNERSHIP_FILE = 'state-ownership.json';\n\n/** Load ownership metadata from the harness directory. */\nexport function loadOwnership(harnessDir: string): StateOwnership {\n const ownershipPath = join(harnessDir, 'memory', OWNERSHIP_FILE);\n if (!existsSync(ownershipPath)) {\n return { ...DEFAULT_OWNERSHIP };\n }\n\n try {\n const raw = readFileSync(ownershipPath, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<StateOwnership>;\n return { ...DEFAULT_OWNERSHIP, ...parsed };\n } catch {\n return { ...DEFAULT_OWNERSHIP };\n }\n}\n\n/** Save ownership metadata to the harness directory. */\nexport function saveOwnership(harnessDir: string, ownership: StateOwnership): void {\n const memoryDir = join(harnessDir, 'memory');\n const ownershipPath = join(memoryDir, OWNERSHIP_FILE);\n withFileLockSync(harnessDir, ownershipPath, () => {\n writeFileSync(ownershipPath, JSON.stringify(ownership, null, 2), 'utf-8');\n });\n}\n\n// ─── Merge Logic ─────────────────────────────────────────────────────────────\n\n/**\n * Merge a state change into the current state, respecting ownership.\n *\n * Rules:\n * - `human-wins`: If a human-owned field is being changed by an agent, the human value is kept.\n * - `agent-wins`: If an agent-owned field is being changed by a human, the agent value is kept.\n * - `latest-wins`: The most recent change always wins (default).\n * - `union`: For array fields (goals, active_workflows, unfinished_business), merge by union.\n * For scalar fields, latest-wins.\n *\n * @param harnessDir - Harness directory path\n * @param change - The state change to apply\n * @param strategy - Merge strategy (default: 'human-wins')\n */\nexport function mergeState(\n harnessDir: string,\n change: OwnedStateChange,\n strategy: MergeStrategy = 'human-wins',\n): MergeResult {\n const currentState = loadState(harnessDir);\n const ownership = loadOwnership(harnessDir);\n const conflicts: StateConflict[] = [];\n\n const mergedState = { ...currentState };\n const mergedOwnership = { ...ownership };\n\n const fields = Object.keys(change.changes) as Array<keyof AgentState>;\n\n for (const field of fields) {\n const newValue = change.changes[field];\n if (newValue === undefined) continue;\n\n const currentOwner = ownership[field];\n const changeAuthor = change.author;\n\n // Same owner → no conflict, apply directly\n if (currentOwner === changeAuthor) {\n applyField(mergedState, field, newValue);\n continue;\n }\n\n // Different owner → potential conflict\n const currentValue = currentState[field];\n\n // Check if values actually differ\n if (valuesEqual(currentValue, newValue)) {\n continue; // No actual conflict\n }\n\n // Resolve conflict based on strategy\n const resolved = resolveConflict(\n field,\n currentValue,\n newValue,\n currentOwner,\n changeAuthor,\n strategy,\n );\n\n conflicts.push({\n field,\n humanValue: currentOwner === 'human' ? currentValue : newValue,\n agentValue: currentOwner === 'agent' ? currentValue : newValue,\n resolvedTo: resolved.winner,\n resolvedValue: resolved.value,\n });\n\n applyField(mergedState, field, resolved.value);\n mergedOwnership[field] = resolved.winner;\n }\n\n // Update ownership for non-conflicting changes\n for (const field of fields) {\n if (!conflicts.some((c) => c.field === field)) {\n mergedOwnership[field] = change.author;\n }\n }\n\n // Update last_interaction\n mergedState.last_interaction = change.timestamp ?? new Date().toISOString();\n mergedOwnership.last_interaction = 'infrastructure';\n\n // Persist\n saveState(harnessDir, mergedState);\n saveOwnership(harnessDir, mergedOwnership);\n\n return {\n state: mergedState,\n ownership: mergedOwnership,\n conflicts,\n hadConflicts: conflicts.length > 0,\n };\n}\n\n/**\n * Apply a state change without ownership — direct write.\n * Use this when ownership tracking is not needed.\n */\nexport function applyStateChange(\n harnessDir: string,\n changes: Partial<AgentState>,\n): AgentState {\n const currentState = loadState(harnessDir);\n const mergedState = { ...currentState };\n\n for (const [key, value] of Object.entries(changes)) {\n if (value !== undefined) {\n applyField(mergedState, key as keyof AgentState, value);\n }\n }\n\n saveState(harnessDir, mergedState);\n return mergedState;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction resolveConflict(\n field: keyof AgentState,\n currentValue: unknown,\n newValue: unknown,\n currentOwner: StateOwner,\n changeAuthor: StateOwner,\n strategy: MergeStrategy,\n): { value: unknown; winner: StateOwner } {\n switch (strategy) {\n case 'human-wins':\n if (currentOwner === 'human') {\n return { value: currentValue, winner: 'human' };\n }\n if (changeAuthor === 'human') {\n return { value: newValue, winner: 'human' };\n }\n // Neither is human — latest wins\n return { value: newValue, winner: changeAuthor };\n\n case 'agent-wins':\n if (currentOwner === 'agent') {\n return { value: currentValue, winner: 'agent' };\n }\n if (changeAuthor === 'agent') {\n return { value: newValue, winner: 'agent' };\n }\n return { value: newValue, winner: changeAuthor };\n\n case 'latest-wins':\n return { value: newValue, winner: changeAuthor };\n\n case 'union':\n if (isArrayField(field) && Array.isArray(currentValue) && Array.isArray(newValue)) {\n const union = [...new Set([...currentValue, ...newValue])];\n return { value: union, winner: changeAuthor };\n }\n // Non-array fields: latest wins\n return { value: newValue, winner: changeAuthor };\n\n default:\n return { value: newValue, winner: changeAuthor };\n }\n}\n\nfunction isArrayField(field: keyof AgentState): boolean {\n return field === 'goals' || field === 'active_workflows' || field === 'unfinished_business';\n}\n\nfunction applyField(state: AgentState, field: keyof AgentState, value: unknown): void {\n switch (field) {\n case 'mode':\n state.mode = value as string;\n break;\n case 'goals':\n state.goals = value as string[];\n break;\n case 'active_workflows':\n state.active_workflows = value as string[];\n break;\n case 'last_interaction':\n state.last_interaction = value as string;\n break;\n case 'unfinished_business':\n state.unfinished_business = value as string[];\n break;\n }\n}\n\nfunction valuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((v, i) => v === b[i]);\n }\n return JSON.stringify(a) === JSON.stringify(b);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/runtime/state-merge.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync } from 'fs';\nimport { join } from 'path';\nimport type { AgentState } from '../core/types.js';\nimport { loadState, saveState } from './state.js';\nimport { withFileLockSync } from './file-lock.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport type StateOwner = 'human' | 'agent' | 'infrastructure';\n\n/** Tracks which entity last modified each field of the state. */\nexport interface StateOwnership {\n mode: StateOwner;\n goals: StateOwner;\n active_workflows: StateOwner;\n last_interaction: StateOwner;\n unfinished_business: StateOwner;\n}\n\n/** A state change with ownership metadata. */\nexport interface OwnedStateChange {\n /** Who is making this change */\n author: StateOwner;\n /** Partial state to merge — only provided fields are updated */\n changes: Partial<AgentState>;\n /** Timestamp of the change (ISO string) */\n timestamp?: string;\n}\n\n/** Strategy for resolving conflicting state changes. */\nexport type MergeStrategy = 'human-wins' | 'agent-wins' | 'latest-wins' | 'union';\n\nexport interface MergeResult {\n /** The merged state */\n state: AgentState;\n /** Updated ownership */\n ownership: StateOwnership;\n /** Fields that had conflicts */\n conflicts: StateConflict[];\n /** Whether any conflicts were resolved */\n hadConflicts: boolean;\n}\n\nexport interface StateConflict {\n field: keyof AgentState;\n humanValue: unknown;\n agentValue: unknown;\n resolvedTo: StateOwner;\n resolvedValue: unknown;\n}\n\n// ─── Ownership Tracking ─────────────────────────────────────────────────────\n\nconst DEFAULT_OWNERSHIP: StateOwnership = {\n mode: 'agent',\n goals: 'human',\n active_workflows: 'agent',\n last_interaction: 'infrastructure',\n unfinished_business: 'agent',\n};\n\nconst OWNERSHIP_FILE = 'state-ownership.json';\n\n/** Load ownership metadata from the harness directory. */\nexport function loadOwnership(harnessDir: string): StateOwnership {\n const ownershipPath = join(harnessDir, 'memory', OWNERSHIP_FILE);\n if (!existsSync(ownershipPath)) {\n return { ...DEFAULT_OWNERSHIP };\n }\n\n try {\n const raw = readFileSync(ownershipPath, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<StateOwnership>;\n return { ...DEFAULT_OWNERSHIP, ...parsed };\n } catch {\n return { ...DEFAULT_OWNERSHIP };\n }\n}\n\n/** Save ownership metadata to the harness directory. */\nexport function saveOwnership(harnessDir: string, ownership: StateOwnership): void {\n const memoryDir = join(harnessDir, 'memory');\n const ownershipPath = join(memoryDir, OWNERSHIP_FILE);\n withFileLockSync(harnessDir, ownershipPath, () => {\n writeFileSync(ownershipPath, JSON.stringify(ownership, null, 2), 'utf-8');\n });\n}\n\n// ─── Merge Logic ─────────────────────────────────────────────────────────────\n\n/**\n * Merge a state change into the current state, respecting ownership.\n *\n * Rules:\n * - `human-wins`: If a human-owned field is being changed by an agent, the human value is kept.\n * - `agent-wins`: If an agent-owned field is being changed by a human, the agent value is kept.\n * - `latest-wins`: The most recent change always wins (default).\n * - `union`: For array fields (goals, active_workflows, unfinished_business), merge by union.\n * For scalar fields, latest-wins.\n *\n * @param harnessDir - Harness directory path\n * @param change - The state change to apply\n * @param strategy - Merge strategy (default: 'human-wins')\n */\nexport function mergeState(\n harnessDir: string,\n change: OwnedStateChange,\n strategy: MergeStrategy = 'human-wins',\n): MergeResult {\n const currentState = loadState(harnessDir);\n const ownership = loadOwnership(harnessDir);\n const conflicts: StateConflict[] = [];\n\n const mergedState = { ...currentState };\n const mergedOwnership = { ...ownership };\n\n const fields = Object.keys(change.changes) as Array<keyof AgentState>;\n\n for (const field of fields) {\n const newValue = change.changes[field];\n if (newValue === undefined) continue;\n\n const currentOwner = ownership[field];\n const changeAuthor = change.author;\n\n // Same owner → no conflict, apply directly\n if (currentOwner === changeAuthor) {\n applyField(mergedState, field, newValue);\n continue;\n }\n\n // Different owner → potential conflict\n const currentValue = currentState[field];\n\n // Check if values actually differ\n if (valuesEqual(currentValue, newValue)) {\n continue; // No actual conflict\n }\n\n // Resolve conflict based on strategy\n const resolved = resolveConflict(\n field,\n currentValue,\n newValue,\n currentOwner,\n changeAuthor,\n strategy,\n );\n\n conflicts.push({\n field,\n humanValue: currentOwner === 'human' ? currentValue : newValue,\n agentValue: currentOwner === 'agent' ? currentValue : newValue,\n resolvedTo: resolved.winner,\n resolvedValue: resolved.value,\n });\n\n applyField(mergedState, field, resolved.value);\n mergedOwnership[field] = resolved.winner;\n }\n\n // Update ownership for non-conflicting changes\n for (const field of fields) {\n if (!conflicts.some((c) => c.field === field)) {\n mergedOwnership[field] = change.author;\n }\n }\n\n // Update last_interaction\n mergedState.last_interaction = change.timestamp ?? new Date().toISOString();\n mergedOwnership.last_interaction = 'infrastructure';\n\n // Persist\n saveState(harnessDir, mergedState);\n saveOwnership(harnessDir, mergedOwnership);\n\n return {\n state: mergedState,\n ownership: mergedOwnership,\n conflicts,\n hadConflicts: conflicts.length > 0,\n };\n}\n\n/**\n * Apply a state change without ownership — direct write.\n * Use this when ownership tracking is not needed.\n */\nexport function applyStateChange(\n harnessDir: string,\n changes: Partial<AgentState>,\n): AgentState {\n const currentState = loadState(harnessDir);\n const mergedState = { ...currentState };\n\n for (const [key, value] of Object.entries(changes)) {\n if (value !== undefined) {\n applyField(mergedState, key as keyof AgentState, value);\n }\n }\n\n saveState(harnessDir, mergedState);\n return mergedState;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction resolveConflict(\n field: keyof AgentState,\n currentValue: unknown,\n newValue: unknown,\n currentOwner: StateOwner,\n changeAuthor: StateOwner,\n strategy: MergeStrategy,\n): { value: unknown; winner: StateOwner } {\n switch (strategy) {\n case 'human-wins':\n if (currentOwner === 'human') {\n return { value: currentValue, winner: 'human' };\n }\n if (changeAuthor === 'human') {\n return { value: newValue, winner: 'human' };\n }\n // Neither is human — latest wins\n return { value: newValue, winner: changeAuthor };\n\n case 'agent-wins':\n if (currentOwner === 'agent') {\n return { value: currentValue, winner: 'agent' };\n }\n if (changeAuthor === 'agent') {\n return { value: newValue, winner: 'agent' };\n }\n return { value: newValue, winner: changeAuthor };\n\n case 'latest-wins':\n return { value: newValue, winner: changeAuthor };\n\n case 'union':\n if (isArrayField(field) && Array.isArray(currentValue) && Array.isArray(newValue)) {\n const union = [...new Set([...currentValue, ...newValue])];\n return { value: union, winner: changeAuthor };\n }\n // Non-array fields: latest wins\n return { value: newValue, winner: changeAuthor };\n\n default:\n return { value: newValue, winner: changeAuthor };\n }\n}\n\nfunction isArrayField(field: keyof AgentState): boolean {\n return field === 'goals' || field === 'active_workflows' || field === 'unfinished_business';\n}\n\nfunction applyField(state: AgentState, field: keyof AgentState, value: unknown): void {\n switch (field) {\n case 'mode':\n state.mode = value as string;\n break;\n case 'goals':\n state.goals = value as string[];\n break;\n case 'active_workflows':\n state.active_workflows = value as string[];\n break;\n case 'last_interaction':\n state.last_interaction = value as string;\n break;\n case 'unfinished_business':\n state.unfinished_business = value as string[];\n break;\n }\n}\n\nfunction valuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((v, i) => v === b[i]);\n }\n return JSON.stringify(a) === JSON.stringify(b);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
collectSnapshot,
|
|
5
5
|
formatDashboard
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-5O5OGOOQ.js";
|
|
7
7
|
import "./chunk-6EMOEYGU.js";
|
|
8
8
|
import "./chunk-GNUSHD2Y.js";
|
|
9
9
|
import "./chunk-5H34JPMB.js";
|
|
@@ -12,11 +12,10 @@ import "./chunk-Z2PUCXTZ.js";
|
|
|
12
12
|
import "./chunk-TAT6JU3X.js";
|
|
13
13
|
import "./chunk-JKMGYWXB.js";
|
|
14
14
|
import "./chunk-BSKDOFRT.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-ZZJOFKAT.js";
|
|
15
|
+
import "./chunk-EC42HQQH.js";
|
|
16
|
+
import "./chunk-4TQQZILG.js";
|
|
18
17
|
export {
|
|
19
18
|
collectSnapshot,
|
|
20
19
|
formatDashboard
|
|
21
20
|
};
|
|
22
|
-
//# sourceMappingURL=telemetry-
|
|
21
|
+
//# sourceMappingURL=telemetry-RS2JZUZP.js.map
|
|
@@ -9,12 +9,11 @@ import {
|
|
|
9
9
|
executeHttpOperation,
|
|
10
10
|
getToolSetSummary,
|
|
11
11
|
resolveEndpoint
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-5CO5JTYT.js";
|
|
13
|
+
import "./chunk-PMFAYKBD.js";
|
|
14
|
+
import "./chunk-2UVWCTAY.js";
|
|
15
15
|
import "./chunk-BSKDOFRT.js";
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-ZZJOFKAT.js";
|
|
16
|
+
import "./chunk-4TQQZILG.js";
|
|
18
17
|
export {
|
|
19
18
|
buildAuthHeaders,
|
|
20
19
|
buildOperationSchema,
|
|
@@ -25,4 +24,4 @@ export {
|
|
|
25
24
|
getToolSetSummary,
|
|
26
25
|
resolveEndpoint
|
|
27
26
|
};
|
|
28
|
-
//# sourceMappingURL=tool-executor-
|
|
27
|
+
//# sourceMappingURL=tool-executor-6I5PHQDY.js.map
|
|
@@ -6,10 +6,9 @@ import {
|
|
|
6
6
|
listToolSummaries,
|
|
7
7
|
loadTools,
|
|
8
8
|
parseToolDefinition
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-ZZJOFKAT.js";
|
|
9
|
+
} from "./chunk-PMFAYKBD.js";
|
|
10
|
+
import "./chunk-2UVWCTAY.js";
|
|
11
|
+
import "./chunk-4TQQZILG.js";
|
|
13
12
|
export {
|
|
14
13
|
checkToolAuth,
|
|
15
14
|
getToolById,
|
|
@@ -17,4 +16,4 @@ export {
|
|
|
17
16
|
loadTools,
|
|
18
17
|
parseToolDefinition
|
|
19
18
|
};
|
|
20
|
-
//# sourceMappingURL=tools-
|
|
19
|
+
//# sourceMappingURL=tools-NDFJNVHK.js.map
|
|
@@ -6,8 +6,7 @@ import {
|
|
|
6
6
|
FrontmatterSchema,
|
|
7
7
|
HarnessConfigSchema,
|
|
8
8
|
getPrimitiveDirs
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-ZZJOFKAT.js";
|
|
9
|
+
} from "./chunk-4TQQZILG.js";
|
|
11
10
|
export {
|
|
12
11
|
CONFIG_DEFAULTS,
|
|
13
12
|
CORE_PRIMITIVE_DIRS,
|
|
@@ -15,4 +14,4 @@ export {
|
|
|
15
14
|
HarnessConfigSchema,
|
|
16
15
|
getPrimitiveDirs
|
|
17
16
|
};
|
|
18
|
-
//# sourceMappingURL=types-
|
|
17
|
+
//# sourceMappingURL=types-NPJZAI72.js.map
|
|
@@ -7,13 +7,14 @@ import {
|
|
|
7
7
|
downloadCapability,
|
|
8
8
|
fixCapability,
|
|
9
9
|
installCapability
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-UXCHAS3Z.js";
|
|
11
|
+
import "./chunk-D7AWV24Z.js";
|
|
12
12
|
import "./chunk-Z2PUCXTZ.js";
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
import "./chunk-2UVWCTAY.js";
|
|
14
|
+
import {
|
|
15
|
+
log
|
|
16
|
+
} from "./chunk-BSKDOFRT.js";
|
|
17
|
+
import "./chunk-4TQQZILG.js";
|
|
17
18
|
|
|
18
19
|
// src/runtime/universal-installer.ts
|
|
19
20
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "fs";
|
|
@@ -228,6 +229,35 @@ async function recordProvenance(content, originalSource) {
|
|
|
228
229
|
data.installed_by = `agent-harness@${getHarnessVersion()}`;
|
|
229
230
|
return matter.stringify(parsed.content, data);
|
|
230
231
|
}
|
|
232
|
+
function evaluateLicensePolicy(detected, policy, forceLicense) {
|
|
233
|
+
if (forceLicense) {
|
|
234
|
+
return {
|
|
235
|
+
action: "allow",
|
|
236
|
+
reason: `forced license override: ${forceLicense}`,
|
|
237
|
+
spdxId: forceLicense
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const spdxId = detected.spdxId;
|
|
241
|
+
if (spdxId === "PROPRIETARY") {
|
|
242
|
+
return {
|
|
243
|
+
action: policy.on_proprietary,
|
|
244
|
+
reason: detected.licenseSource ? `proprietary content per ${detected.licenseSource}` : 'proprietary content (license text says "all rights reserved")',
|
|
245
|
+
spdxId
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (policy.allowed_licenses.includes(spdxId)) {
|
|
249
|
+
return {
|
|
250
|
+
action: "allow",
|
|
251
|
+
reason: `${spdxId} is in allowed_licenses`,
|
|
252
|
+
spdxId
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
action: policy.on_unknown_license,
|
|
257
|
+
reason: spdxId === "UNKNOWN" ? "no LICENSE file found in source repo (per-file, repo root, or frontmatter)" : `${spdxId} is not in your allowed_licenses (config.yaml install.allowed_licenses)`,
|
|
258
|
+
spdxId
|
|
259
|
+
};
|
|
260
|
+
}
|
|
231
261
|
var VALID_TYPES = ["rule", "instinct", "skill", "playbook", "workflow", "tool", "agent"];
|
|
232
262
|
var TYPE_DIRS = {
|
|
233
263
|
rule: "rules",
|
|
@@ -638,6 +668,59 @@ async function universalInstall(harnessDir, source, options) {
|
|
|
638
668
|
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
639
669
|
finalContent = await recordProvenance(finalContent, source);
|
|
640
670
|
result.fixes.push("Recorded provenance (source, installed_at, installed_by)");
|
|
671
|
+
try {
|
|
672
|
+
const { loadConfig } = await import("./config-PYSS3QY6.js");
|
|
673
|
+
const config = loadConfig(harnessDir);
|
|
674
|
+
const installPolicy = config.install;
|
|
675
|
+
const parsed = matter(finalContent);
|
|
676
|
+
const detected = {
|
|
677
|
+
spdxId: typeof parsed.data.license === "string" ? parsed.data.license : "UNKNOWN",
|
|
678
|
+
copyright: typeof parsed.data.copyright === "string" ? parsed.data.copyright : void 0,
|
|
679
|
+
licenseSource: typeof parsed.data.license_source === "string" ? parsed.data.license_source : void 0
|
|
680
|
+
};
|
|
681
|
+
const decision = evaluateLicensePolicy(detected, installPolicy, options?.forceLicense);
|
|
682
|
+
if (decision.action === "block") {
|
|
683
|
+
result.errors.push(
|
|
684
|
+
`License policy blocked install: ${decision.reason}` + (detected.licenseSource ? ` (${detected.licenseSource})` : "") + `. To override, re-run with --force-license <SPDX> if you have written permission.`
|
|
685
|
+
);
|
|
686
|
+
return result;
|
|
687
|
+
}
|
|
688
|
+
if (decision.action === "warn") {
|
|
689
|
+
log.warn(
|
|
690
|
+
`[install] license policy warning: ${decision.reason}. Installing anyway (set install.on_unknown_license: block in config.yaml to refuse).`
|
|
691
|
+
);
|
|
692
|
+
result.fixes.push(`License policy: warned (${decision.spdxId})`);
|
|
693
|
+
}
|
|
694
|
+
if (decision.action === "prompt") {
|
|
695
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
696
|
+
const readline = await import("readline");
|
|
697
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
698
|
+
const question = `
|
|
699
|
+
\u26A0 License policy: ${decision.reason}
|
|
700
|
+
${detected.licenseSource ? `License source: ${detected.licenseSource}
|
|
701
|
+
` : ""}Install anyway? [y/N] `;
|
|
702
|
+
const answer = await new Promise((res) => rl.question(question, res));
|
|
703
|
+
rl.close();
|
|
704
|
+
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
705
|
+
result.errors.push("License policy prompt declined by user");
|
|
706
|
+
return result;
|
|
707
|
+
}
|
|
708
|
+
result.fixes.push(`License policy: prompted, user accepted (${decision.spdxId})`);
|
|
709
|
+
} else {
|
|
710
|
+
result.errors.push(
|
|
711
|
+
`License policy requires interactive confirmation (${decision.reason}) but stdin/stdout is not a TTY. Re-run interactively, or pass --force-license <SPDX> if you have written permission.`
|
|
712
|
+
);
|
|
713
|
+
return result;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (decision.action === "allow" && options?.forceLicense) {
|
|
717
|
+
result.fixes.push(`License policy: forced to ${decision.spdxId}`);
|
|
718
|
+
}
|
|
719
|
+
} catch (err) {
|
|
720
|
+
log.warn(
|
|
721
|
+
`[install] license policy check skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
641
724
|
}
|
|
642
725
|
const tempDir = join(tmpdir(), "harness-install");
|
|
643
726
|
mkdirSync(tempDir, { recursive: true });
|
|
@@ -782,6 +865,7 @@ export {
|
|
|
782
865
|
convertToRawUrl,
|
|
783
866
|
detectFormat,
|
|
784
867
|
detectLicense,
|
|
868
|
+
evaluateLicensePolicy,
|
|
785
869
|
installFromFile,
|
|
786
870
|
installFromUrl,
|
|
787
871
|
normalizeToHarness,
|
|
@@ -789,4 +873,4 @@ export {
|
|
|
789
873
|
resolveSource,
|
|
790
874
|
universalInstall
|
|
791
875
|
};
|
|
792
|
-
//# sourceMappingURL=universal-installer-
|
|
876
|
+
//# sourceMappingURL=universal-installer-LCAZHFZR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime/universal-installer.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'fs';\nimport { join, basename, extname } from 'path';\nimport { tmpdir } from 'os';\nimport { createRequire } from 'module';\nimport matter from 'gray-matter';\nimport { parse as parseYaml } from 'yaml';\nimport { fixCapability, installCapability, downloadCapability } from './intake.js';\nimport { discoverSources } from './sources.js';\nimport { log } from '../core/logger.js';\n\n// ─── Provenance ──────────────────────────────────────────────────────────────\n\n/**\n * Read the harness's own package.json version for the `installed_by` field.\n *\n * Has to handle three possible runtime layouts because tsup bundles flat:\n * - Dev/test: src/runtime/universal-installer.ts → ../../package.json\n * - Built bin: dist/cli/index.js → ../../package.json\n * - Built lib: dist/<bundle>.js → ../package.json\n *\n * Walks up one directory at a time, requires `package.json`, and returns\n * the version of the FIRST one whose name is `@agntk/agent-harness`. Stops\n * after a few levels so a broken environment never causes an infinite loop.\n * Returns \"unknown\" on any failure so an install never blocks on this.\n */\nfunction getHarnessVersion(): string {\n try {\n const require = createRequire(import.meta.url);\n const candidates = [\n '../package.json',\n '../../package.json',\n '../../../package.json',\n ];\n for (const candidate of candidates) {\n try {\n const pkg = require(candidate) as { name?: string; version?: string };\n if (pkg.name === '@agntk/agent-harness' && pkg.version) {\n return pkg.version;\n }\n } catch {\n // Candidate didn't resolve — try the next one.\n }\n }\n return 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * Resolve a commit SHA for a GitHub raw URL by calling the GitHub Contents API.\n *\n * Input URL shape:\n * https://raw.githubusercontent.com/{owner}/{repo}/{ref}/{path}\n * where {ref} is either a 40-char commit SHA or a branch/tag name.\n *\n * Returns the SHA (either the one already in the URL, or the one resolved from\n * a branch name via the Contents API). Returns `null` on any failure — network\n * error, timeout, 404, non-github host, unparseable URL — so the install can\n * proceed without source_commit.\n */\nasync function resolveGithubCommitSha(url: string): Promise<string | null> {\n // Only handle raw.githubusercontent.com URLs\n const match = url.match(\n /^https?:\\/\\/raw\\.githubusercontent\\.com\\/([^/]+)\\/([^/]+)\\/([^/]+)\\/(.+)$/,\n );\n if (!match) return null;\n const [, owner, repo, ref, path] = match;\n\n // If ref is already a 40-char hex SHA, just return it\n if (/^[0-9a-f]{40}$/i.test(ref)) return ref;\n\n // Otherwise resolve via the Contents API\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${ref}`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n try {\n const response = await fetch(apiUrl, {\n signal: controller.signal,\n headers: { 'Accept': 'application/vnd.github+json' },\n });\n if (!response.ok) return null;\n const data = (await response.json()) as { sha?: string };\n if (typeof data.sha === 'string' && /^[0-9a-f]{40}$/i.test(data.sha)) {\n return data.sha;\n }\n return null;\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n// ─── License Detection (Level 2 of task 12.14) ──────────────────────────────\n\n/**\n * Result of license detection. `spdxId` is one of:\n * - A standard SPDX identifier (\"MIT\", \"Apache-2.0\", etc.)\n * - \"PROPRIETARY\" — text says \"all rights reserved\" or no permission grant\n * - \"UNKNOWN\" — no LICENSE file found, or text doesn't match any pattern\n */\nexport interface LicenseInfo {\n /** SPDX id, \"PROPRIETARY\", or \"UNKNOWN\" */\n spdxId: string;\n /** First copyright line found in the LICENSE text, if any */\n copyright?: string;\n /** URL to the license file the detector actually found, if any */\n licenseSource?: string;\n}\n\n/** Sibling LICENSE filenames to probe in the same directory as the installed file. */\nconst SIBLING_LICENSE_NAMES = [\n 'LICENSE',\n 'LICENSE.txt',\n 'LICENSE.md',\n 'COPYING',\n 'COPYING.txt',\n] as const;\n\n/**\n * Classify a LICENSE file's body text into an SPDX id, \"PROPRIETARY\", or \"UNKNOWN\".\n * Substring-based detection — not a full parser. Good enough for the common cases\n * (MIT, Apache-2.0, BSD, ISC, GPL, MPL, CC) and the proprietary \"all rights reserved\"\n * pattern that bit us in v0.1.0.\n */\nfunction classifyLicenseText(text: string): string {\n const lower = text.toLowerCase();\n // PROPRIETARY check first — overrides any false-positive substring match below.\n if (lower.includes('all rights reserved')) {\n return 'PROPRIETARY';\n }\n // Then SPDX-by-substring. Order matters: check more-specific patterns first\n // (e.g. AGPL before GPL, LGPL before GPL).\n if (lower.includes('mit license')) return 'MIT';\n if (lower.includes('apache license, version 2.0') || lower.includes('apache-2.0'))\n return 'Apache-2.0';\n if (lower.includes('mozilla public license version 2.0') || lower.includes('mpl-2.0'))\n return 'MPL-2.0';\n if (lower.includes('gnu affero general public license')) return 'AGPL-3.0';\n if (lower.includes('gnu lesser general public license')) {\n if (lower.includes('version 3')) return 'LGPL-3.0';\n if (lower.includes('version 2')) return 'LGPL-2.1';\n }\n if (lower.includes('gnu general public license')) {\n if (lower.includes('version 3')) return 'GPL-3.0';\n if (lower.includes('version 2')) return 'GPL-2.0';\n }\n if (lower.includes('isc license')) return 'ISC';\n if (lower.includes('cc0 1.0 universal') || lower.includes('cc0-1.0')) return 'CC0-1.0';\n if (lower.includes('creative commons attribution-sharealike 4.0')) return 'CC-BY-SA-4.0';\n if (lower.includes('creative commons attribution 4.0') || lower.includes('cc-by-4.0'))\n return 'CC-BY-4.0';\n if (\n lower.includes('redistribution and use in source and binary forms') &&\n lower.includes('neither the name of')\n ) {\n return 'BSD-3-Clause';\n }\n if (lower.includes('redistribution and use in source and binary forms')) {\n return 'BSD-2-Clause';\n }\n if (lower.includes('this is free and unencumbered software released into the public domain')) {\n return 'Unlicense';\n }\n return 'UNKNOWN';\n}\n\n/**\n * Extract the first `Copyright (c) YEAR ...` line from a license body.\n * Returns the trimmed line, or undefined if no copyright line is found.\n */\nfunction extractCopyright(text: string): string | undefined {\n // Match lines starting with \"©\" or \"Copyright\" (any case) and containing a\n // 4-digit year. The leading \"©\" can be followed immediately by space/digit\n // (it's a non-word char so we don't put a \\b after it). \"Copyright\" can be\n // followed by anything as long as a year appears later in the line.\n const lines = text.split(/\\r?\\n/);\n for (const line of lines) {\n const trimmed = line.trim();\n if (/^©\\s*\\d{4}/.test(trimmed) || /^copyright\\b.*\\d{4}/i.test(trimmed)) {\n return trimmed;\n }\n }\n return undefined;\n}\n\n/**\n * Try to fetch a single sibling LICENSE file next to the installed file.\n * Returns the body text on success, null on any failure (404, network, timeout).\n */\nasync function fetchSiblingLicense(\n siblingUrl: string,\n): Promise<string | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n try {\n const response = await fetch(siblingUrl, { signal: controller.signal });\n if (!response.ok) return null;\n return await response.text();\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * Try to fetch the repo-root LICENSE via the GitHub License API.\n * Returns the SPDX id, html_url, and (when available) the decoded body text\n * so the caller can extract a copyright line. Null on any failure.\n *\n * The API response shape:\n * {\n * license: { spdx_id: \"MIT\" },\n * html_url: \"https://github.com/owner/repo/blob/main/LICENSE\",\n * content: \"<base64>\",\n * encoding: \"base64\"\n * }\n *\n * https://docs.github.com/en/rest/licenses/licenses#get-the-license-for-a-repository\n */\nasync function fetchGithubRepoLicense(\n owner: string,\n repo: string,\n): Promise<{ spdxId: string; htmlUrl: string; body?: string } | null> {\n const apiUrl = `https://api.github.com/repos/${owner}/${repo}/license`;\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n try {\n const response = await fetch(apiUrl, {\n signal: controller.signal,\n headers: { 'Accept': 'application/vnd.github+json' },\n });\n if (!response.ok) return null;\n const data = (await response.json()) as {\n license?: { spdx_id?: string };\n html_url?: string;\n content?: string;\n encoding?: string;\n };\n const spdxId = data.license?.spdx_id;\n if (!spdxId || spdxId === 'NOASSERTION') return null;\n\n // Decode the base64-encoded body so the caller can extract copyright.\n // Tolerant of failures — if decoding throws, just omit the body.\n let body: string | undefined;\n if (data.content && data.encoding === 'base64') {\n try {\n body = Buffer.from(data.content, 'base64').toString('utf-8');\n } catch {\n body = undefined;\n }\n }\n\n return { spdxId, htmlUrl: data.html_url ?? '', body };\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/**\n * Detect the license of a file at a given URL.\n *\n * Lookup order, strictest finding wins:\n * 1. Per-file LICENSE sibling in the same directory as the file.\n * Catches the v0.1.0 case where each anthropics/skills/<skill>/\n * directory contained its own proprietary LICENSE.txt.\n * 2. Repository root LICENSE via the GitHub License API. Returns SPDX id.\n * 3. Caller falls back to the source file's own frontmatter (handled in\n * recordProvenance, not here).\n *\n * Strictness rule: PROPRIETARY > UNKNOWN > permissive SPDX. If a per-file\n * LICENSE says \"All rights reserved\" we never look at the repo root —\n * proprietary always wins.\n *\n * Non-github URLs return immediately with `{ spdxId: 'UNKNOWN' }` since we\n * have no way to look up a license. The caller can still use frontmatter.\n *\n * @param sourceUrl The URL the user passed to `harness install`\n * @returns LicenseInfo. Always returns an object — never throws or returns null.\n */\nexport async function detectLicense(sourceUrl: string): Promise<LicenseInfo> {\n // Only github raw URLs have a structure we can probe.\n const match = sourceUrl.match(\n /^https?:\\/\\/raw\\.githubusercontent\\.com\\/([^/]+)\\/([^/]+)\\/([^/]+)\\/(.+)$/,\n );\n if (!match) {\n return { spdxId: 'UNKNOWN' };\n }\n const [, owner, repo, ref, path] = match;\n\n // 1. Per-file LICENSE sibling — split path into dir, then probe each filename.\n const lastSlash = path.lastIndexOf('/');\n const dir = lastSlash >= 0 ? path.slice(0, lastSlash) : '';\n const dirPrefix = dir ? `${dir}/` : '';\n\n for (const siblingName of SIBLING_LICENSE_NAMES) {\n const siblingUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${dirPrefix}${siblingName}`;\n const text = await fetchSiblingLicense(siblingUrl);\n if (text) {\n const spdxId = classifyLicenseText(text);\n const copyright = extractCopyright(text);\n return { spdxId, copyright, licenseSource: siblingUrl };\n }\n }\n\n // 2. Repository root LICENSE via the GitHub License API. The API returns\n // the SPDX id directly, plus a base64-encoded body we can use to extract\n // a copyright line.\n const repoLicense = await fetchGithubRepoLicense(owner, repo);\n if (repoLicense) {\n const copyright = repoLicense.body\n ? extractCopyright(repoLicense.body)\n : undefined;\n return {\n spdxId: repoLicense.spdxId,\n copyright,\n licenseSource: repoLicense.htmlUrl || undefined,\n };\n }\n\n return { spdxId: 'UNKNOWN' };\n}\n\n/**\n * Inject provenance and license fields into a normalized markdown file's\n * frontmatter.\n *\n * Provenance rules (Level 1 of task 12.14):\n * - `source` and `source_commit` are preserved if already present (idempotent)\n * - `installed_at` and `installed_by` are always updated to reflect the most\n * recent install action\n * - `source_commit` is only written when a SHA could be resolved\n *\n * License rules (Level 2 of task 12.14):\n * - `license`, `copyright`, `license_source` are preserved if already present\n * in the source file's frontmatter (idempotent — author intent wins)\n * - Otherwise detected via detectLicense() and merged in\n * - License detection NEVER blocks the install. Failures result in\n * `license: UNKNOWN` rather than an error.\n *\n * @param content Normalized markdown content with existing frontmatter\n * @param originalSource The exact URL the user passed to `harness install`\n * @returns The content with provenance + license fields merged into frontmatter\n */\nexport async function recordProvenance(\n content: string,\n originalSource: string,\n): Promise<string> {\n let parsed: ReturnType<typeof matter>;\n try {\n parsed = matter(content);\n } catch {\n return content;\n }\n\n const data = parsed.data as Record<string, unknown>;\n\n // Preserve existing source — idempotency rule\n if (!data.source) {\n data.source = originalSource;\n }\n\n // Preserve existing source_commit; only resolve if missing AND URL is github raw\n if (!data.source_commit) {\n const sha = await resolveGithubCommitSha(originalSource);\n if (sha) {\n data.source_commit = sha;\n }\n }\n\n // License detection (Level 2). Idempotent: don't overwrite author-set fields.\n // Only run detection if at least one license-related field is missing — saves\n // the network calls when re-installing files that already carry their license.\n if (!data.license || !data.copyright || !data.license_source) {\n const license = await detectLicense(originalSource);\n if (!data.license) {\n data.license = license.spdxId;\n }\n if (!data.copyright && license.copyright) {\n data.copyright = license.copyright;\n }\n if (!data.license_source && license.licenseSource) {\n data.license_source = license.licenseSource;\n }\n }\n\n // Always update these to reflect the most recent install\n data.installed_at = new Date().toISOString();\n data.installed_by = `agent-harness@${getHarnessVersion()}`;\n\n return matter.stringify(parsed.content, data);\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Detected source format of a file to be installed. */\nexport type SourceFormat =\n | 'harness' // Already harness convention (frontmatter + L0/L1)\n | 'claude-skill' // Claude Code SKILL.md (plain markdown, no frontmatter)\n | 'faf-yaml' // .faf YAML format\n | 'raw-markdown' // Plain markdown with no harness structure\n | 'bash-hook' // Bash/shell script (hook or workflow)\n | 'mcp-config' // MCP server configuration (JSON/YAML)\n | 'unknown';\n\n/** Result of format detection. */\nexport interface FormatDetection {\n /** Detected format */\n format: SourceFormat;\n /** Inferred primitive type (skill, agent, rule, etc.) */\n primitiveType: string | null;\n /** Confidence score (0-1) */\n confidence: number;\n /** Reasons for the detection */\n reasons: string[];\n}\n\n/** Result of a universal install operation. */\nexport interface UniversalInstallResult {\n /** Whether installation succeeded */\n installed: boolean;\n /** Source reference that was resolved */\n source: string;\n /** Detected format */\n format: FormatDetection;\n /** Path where the file was installed */\n destination: string;\n /** Fixes applied during normalization */\n fixes: string[];\n /** Errors encountered */\n errors: string[];\n /** Suggested dependencies to install */\n suggestedDependencies: string[];\n}\n\n/** Options for the universal installer. */\nexport interface UniversalInstallOptions {\n /** Override the detected primitive type (skill, rule, agent, etc.) */\n type?: string;\n /** Override the generated ID */\n id?: string;\n /** Force install even if validation has warnings */\n force?: boolean;\n /** Skip auto-fix (frontmatter, L0/L1 generation) */\n skipFix?: boolean;\n /** Additional tags to add */\n tags?: string[];\n /**\n * Override the license policy for THIS install only. Pass an SPDX id like\n * \"MIT\" or \"Apache-2.0\" — the installer will treat the file as if its\n * license were the override and skip the policy check entirely. Use only\n * when you have written permission for the content. (Level 3 of 12.14.)\n */\n forceLicense?: string;\n}\n\n/**\n * License policy enforcement decision (Level 3 of task 12.14).\n *\n * Returned by `evaluateLicensePolicy()` to tell the installer what to do\n * about a detected license. The installer turns this into a console warning,\n * an interactive prompt, or an error result depending on the action.\n */\nexport interface LicensePolicyDecision {\n /** What the installer should do — block aborts, prompt asks, warn logs, allow continues */\n action: 'allow' | 'warn' | 'prompt' | 'block';\n /** Human-readable reason — used in warning text and block error messages */\n reason: string;\n /** The SPDX id (or PROPRIETARY/UNKNOWN) the policy was evaluated against */\n spdxId: string;\n}\n\n/**\n * Decide what to do about a detected license, given the user's `install:`\n * config policy. Pure function — never makes decisions on its own, never\n * prompts. The caller (universalInstall) handles UI side effects.\n *\n * Logic:\n * - If `forceLicense` is set, always return `allow` (with the override SPDX id)\n * - If the SPDX id is PROPRIETARY → use `on_proprietary` setting\n * - If the SPDX id is in `allowed_licenses` → `allow`\n * - Otherwise (UNKNOWN, GPL, anything not on the list) → use `on_unknown_license`\n */\nexport function evaluateLicensePolicy(\n detected: LicenseInfo,\n policy: {\n allowed_licenses: readonly string[];\n on_unknown_license: 'allow' | 'warn' | 'prompt' | 'block';\n on_proprietary: 'allow' | 'warn' | 'prompt' | 'block';\n },\n forceLicense?: string,\n): LicensePolicyDecision {\n // Force override — caller asserted permission. Skip the check entirely.\n if (forceLicense) {\n return {\n action: 'allow',\n reason: `forced license override: ${forceLicense}`,\n spdxId: forceLicense,\n };\n }\n\n const spdxId = detected.spdxId;\n\n if (spdxId === 'PROPRIETARY') {\n return {\n action: policy.on_proprietary,\n reason: detected.licenseSource\n ? `proprietary content per ${detected.licenseSource}`\n : 'proprietary content (license text says \"all rights reserved\")',\n spdxId,\n };\n }\n\n if (policy.allowed_licenses.includes(spdxId)) {\n return {\n action: 'allow',\n reason: `${spdxId} is in allowed_licenses`,\n spdxId,\n };\n }\n\n // Either UNKNOWN or a non-permissive SPDX id (GPL, AGPL, etc.) the user\n // didn't add to their allowed list.\n return {\n action: policy.on_unknown_license,\n reason:\n spdxId === 'UNKNOWN'\n ? 'no LICENSE file found in source repo (per-file, repo root, or frontmatter)'\n : `${spdxId} is not in your allowed_licenses (config.yaml install.allowed_licenses)`,\n spdxId,\n };\n}\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst VALID_TYPES = ['rule', 'instinct', 'skill', 'playbook', 'workflow', 'tool', 'agent'];\n\nconst TYPE_DIRS: Record<string, string> = {\n rule: 'rules',\n instinct: 'instincts',\n skill: 'skills',\n playbook: 'playbooks',\n workflow: 'workflows',\n tool: 'tools',\n agent: 'agents',\n};\n\n// ─── Format Detection ────────────────────────────────────────────────────────\n\n/**\n * Detect the format of a file based on its content and extension.\n *\n * Detection heuristics:\n * - Has `---` frontmatter with `id:` + `status:` → harness convention\n * - Has `---` frontmatter but missing harness fields → raw-markdown\n * - `.faf` or `.yaml`/`.yml` with `type:` + `content:` keys → faf-yaml\n * - `.sh`/`.bash` or starts with `#!/` → bash-hook\n * - JSON/YAML with `mcpServers` or `servers` → mcp-config\n * - Plain markdown with no frontmatter → claude-skill or raw-markdown\n */\nexport function detectFormat(content: string, filename: string): FormatDetection {\n const ext = extname(filename).toLowerCase();\n const reasons: string[] = [];\n let format: SourceFormat = 'unknown';\n let primitiveType: string | null = null;\n let confidence = 0;\n\n // Check for bash/shell scripts\n if (ext === '.sh' || ext === '.bash' || content.trimStart().startsWith('#!/')) {\n format = 'bash-hook';\n primitiveType = 'workflow';\n confidence = 0.9;\n reasons.push('Shell script detected (shebang or .sh extension)');\n\n // Hooks are typically short scripts with specific patterns\n if (content.includes('hook') || content.includes('pre-commit') || content.includes('post-')) {\n primitiveType = 'workflow';\n reasons.push('Hook pattern detected in content');\n }\n\n return { format, primitiveType, confidence, reasons };\n }\n\n // Check for JSON/YAML MCP configs\n if (ext === '.json') {\n try {\n const parsed = JSON.parse(content) as Record<string, unknown>;\n if (parsed.mcpServers || parsed.servers || parsed.command || parsed.args) {\n format = 'mcp-config';\n primitiveType = 'tool';\n confidence = 0.9;\n reasons.push('MCP configuration JSON detected');\n return { format, primitiveType, confidence, reasons };\n }\n } catch {\n // Not valid JSON, continue\n }\n }\n\n // Check for .faf YAML format\n if (ext === '.faf' || ext === '.yaml' || ext === '.yml') {\n try {\n const parsed = parseYaml(content) as Record<string, unknown>;\n if (parsed.type && parsed.content) {\n format = 'faf-yaml';\n primitiveType = inferTypeFromFafType(String(parsed.type));\n confidence = 0.9;\n reasons.push(`.faf YAML format with type: ${parsed.type}`);\n return { format, primitiveType, confidence, reasons };\n }\n // YAML with mcpServers\n if (parsed.mcpServers || parsed.servers) {\n format = 'mcp-config';\n primitiveType = 'tool';\n confidence = 0.85;\n reasons.push('MCP configuration YAML detected');\n return { format, primitiveType, confidence, reasons };\n }\n } catch {\n // Not valid YAML, continue\n }\n }\n\n // Check for markdown content\n if (ext === '.md' || ext === '' || !ext) {\n // Try to parse frontmatter\n try {\n const parsed = matter(content);\n const data = parsed.data as Record<string, unknown>;\n\n if (data.id && data.status) {\n // Has harness-style frontmatter\n format = 'harness';\n confidence = 0.95;\n reasons.push('Harness frontmatter detected (id + status fields)');\n\n // Detect type from tags\n const tags = Array.isArray(data.tags)\n ? (data.tags as string[]).map((t) => String(t).toLowerCase())\n : [];\n for (const type of VALID_TYPES) {\n if (tags.includes(type)) {\n primitiveType = type;\n break;\n }\n }\n\n return { format, primitiveType, confidence, reasons };\n }\n\n if (Object.keys(data).length > 0) {\n // Has some frontmatter but not harness convention\n format = 'raw-markdown';\n confidence = 0.7;\n reasons.push('Markdown with non-harness frontmatter');\n }\n } catch {\n // No frontmatter or parse error\n }\n\n // Check for Claude Code SKILL.md patterns\n if (format === 'unknown' || format === 'raw-markdown') {\n const isClaudeSkill = detectClaudeSkillPattern(content, filename);\n if (isClaudeSkill) {\n format = 'claude-skill';\n primitiveType = 'skill';\n confidence = 0.8;\n reasons.push('Claude Code SKILL.md pattern detected');\n return { format, primitiveType, confidence, reasons };\n }\n }\n\n // Plain markdown — infer type from content\n if (format === 'unknown') {\n format = 'raw-markdown';\n confidence = 0.5;\n reasons.push('Plain markdown without frontmatter');\n }\n\n // Try to infer type from content/filename\n if (!primitiveType) {\n primitiveType = inferTypeFromContent(content, filename);\n if (primitiveType) {\n reasons.push(`Type inferred from content/filename: ${primitiveType}`);\n }\n }\n\n return { format, primitiveType, confidence, reasons };\n }\n\n return { format, primitiveType, confidence, reasons };\n}\n\n// ─── Format Normalization ────────────────────────────────────────────────────\n\n/**\n * Normalize content from any detected format to harness convention.\n * Returns the normalized markdown content ready for writing.\n */\nexport function normalizeToHarness(\n content: string,\n filename: string,\n detection: FormatDetection,\n options?: UniversalInstallOptions,\n): { content: string; filename: string; fixes: string[] } {\n const fixes: string[] = [];\n const type = options?.type ?? detection.primitiveType;\n\n switch (detection.format) {\n case 'harness':\n // Already in harness format — just pass through\n return { content, filename, fixes: ['Already in harness format'] };\n\n case 'claude-skill':\n return normalizeClaudeSkill(content, filename, type, options, fixes);\n\n case 'faf-yaml':\n return normalizeFafYaml(content, filename, type, options, fixes);\n\n case 'raw-markdown':\n return normalizeRawMarkdown(content, filename, type, options, fixes);\n\n case 'bash-hook':\n return normalizeBashHook(content, filename, type, options, fixes);\n\n case 'mcp-config':\n return normalizeMcpConfig(content, filename, options, fixes);\n\n default:\n return normalizeRawMarkdown(content, filename, type, options, fixes);\n }\n}\n\n/**\n * Convert Claude Code SKILL.md to harness convention.\n * Claude skills are plain markdown — add frontmatter + L0/L1.\n */\nfunction normalizeClaudeSkill(\n content: string,\n filename: string,\n type: string | null,\n options: UniversalInstallOptions | undefined,\n fixes: string[],\n): { content: string; filename: string; fixes: string[] } {\n const id = options?.id ?? deriveId(filename);\n const primitiveType = type ?? 'skill';\n const tags = [primitiveType, ...(options?.tags ?? [])];\n\n // Extract first heading as title\n const headingMatch = content.match(/^#\\s+(.+)$/m);\n const title = headingMatch ? headingMatch[1].trim() : id;\n\n const frontmatter: Record<string, unknown> = {\n id,\n created: new Date().toISOString().split('T')[0],\n author: 'human',\n status: 'active',\n tags,\n };\n\n // Generate L0 from title/first heading\n const l0 = title.length > 120 ? title.slice(0, 117) + '...' : title;\n\n // Generate L1 from first paragraph\n const paragraphs = content.split(/\\n{2,}/).filter((p) => {\n const trimmed = p.trim();\n return trimmed.length > 0 && !trimmed.startsWith('#') && !trimmed.startsWith('<!--');\n });\n const l1 = paragraphs.length > 0\n ? paragraphs[0].replace(/\\n/g, ' ').trim().slice(0, 300)\n : '';\n\n let body = `<!-- L0: ${l0} -->\\n`;\n if (l1) {\n body += `<!-- L1: ${l1} -->\\n`;\n }\n body += '\\n' + content;\n\n const result = matter.stringify(body, frontmatter);\n fixes.push('Added harness frontmatter (id, status, tags)');\n fixes.push(`Generated L0 from heading: \"${l0}\"`);\n if (l1) fixes.push('Generated L1 from first paragraph');\n\n const outFilename = ensureMdExtension(filename);\n return { content: result, filename: outFilename, fixes };\n}\n\n/**\n * Convert .faf YAML format to harness markdown.\n */\nfunction normalizeFafYaml(\n content: string,\n filename: string,\n type: string | null,\n options: UniversalInstallOptions | undefined,\n fixes: string[],\n): { content: string; filename: string; fixes: string[] } {\n let parsed: Record<string, unknown>;\n try {\n parsed = parseYaml(content) as Record<string, unknown>;\n } catch {\n fixes.push('Failed to parse YAML — treating as raw markdown');\n return normalizeRawMarkdown(content, filename, type, options, fixes);\n }\n\n const id = options?.id ?? String(parsed.id ?? deriveId(filename));\n const fafType = String(parsed.type ?? 'skill');\n const primitiveType = type ?? inferTypeFromFafType(fafType) ?? 'skill';\n const title = String(parsed.title ?? parsed.name ?? id);\n const description = String(parsed.description ?? '');\n const fafContent = String(parsed.content ?? '');\n const fafTags = Array.isArray(parsed.tags)\n ? (parsed.tags as string[]).map(String)\n : [];\n\n const tags = [primitiveType, ...fafTags, ...(options?.tags ?? [])];\n\n const frontmatter: Record<string, unknown> = {\n id,\n created: new Date().toISOString().split('T')[0],\n author: 'human',\n status: 'active',\n tags: [...new Set(tags)],\n };\n\n const l0 = title.length > 120 ? title.slice(0, 117) + '...' : title;\n const l1 = description.length > 300 ? description.slice(0, 297) + '...' : description;\n\n let body = `<!-- L0: ${l0} -->\\n`;\n if (l1) body += `<!-- L1: ${l1} -->\\n`;\n body += `\\n# ${title}\\n\\n`;\n if (description) body += `${description}\\n\\n`;\n if (fafContent) body += fafContent + '\\n';\n\n const result = matter.stringify(body, frontmatter);\n fixes.push('Converted .faf YAML to harness markdown');\n fixes.push(`Added frontmatter (id: ${id}, type: ${primitiveType})`);\n\n const outFilename = deriveId(filename) + '.md';\n return { content: result, filename: outFilename, fixes };\n}\n\n/**\n * Normalize raw markdown (no frontmatter or non-harness frontmatter).\n */\nfunction normalizeRawMarkdown(\n content: string,\n filename: string,\n type: string | null,\n options: UniversalInstallOptions | undefined,\n fixes: string[],\n): { content: string; filename: string; fixes: string[] } {\n const id = options?.id ?? deriveId(filename);\n const primitiveType = type ?? 'skill';\n const tags = [primitiveType, ...(options?.tags ?? [])];\n\n // Try to preserve any existing frontmatter\n let parsed: ReturnType<typeof matter>;\n try {\n parsed = matter(content);\n } catch {\n parsed = { data: {}, content, orig: '', excerpt: '', language: '', matter: '', stringify: () => '' } as ReturnType<typeof matter>;\n }\n\n const data = parsed.data as Record<string, unknown>;\n\n // Set required harness fields — options override existing values\n if (options?.id || !data.id) {\n data.id = id;\n fixes.push(`Set id: \"${id}\"`);\n }\n if (!data.status) {\n data.status = 'active';\n fixes.push('Added status: \"active\"');\n }\n if (!data.created) {\n data.created = new Date().toISOString().split('T')[0];\n fixes.push('Added created date');\n }\n if (!data.author || !['human', 'agent', 'infrastructure'].includes(String(data.author))) {\n data.author = 'human';\n fixes.push('Added author: \"human\"');\n }\n if (!Array.isArray(data.tags) || data.tags.length === 0) {\n data.tags = [...new Set(tags)];\n fixes.push(`Added tags: [${(data.tags as string[]).join(', ')}]`);\n }\n\n let body = parsed.content;\n\n // Add L0 if missing\n const l0Regex = /<!--\\s*L0:\\s*(.*?)\\s*-->/;\n if (!l0Regex.test(body)) {\n const headingMatch = body.match(/^#\\s+(.+)$/m);\n const firstLine = body.split('\\n').find((line) => line.trim().length > 0);\n const summary = headingMatch ? headingMatch[1].trim() : (firstLine?.trim() ?? id);\n const l0 = summary.length > 120 ? summary.slice(0, 117) + '...' : summary;\n body = `<!-- L0: ${l0} -->\\n${body}`;\n fixes.push(`Generated L0: \"${l0}\"`);\n }\n\n // Add L1 if missing\n const l1Regex = /<!--\\s*L1:\\s*([\\s\\S]*?)\\s*-->/;\n if (!l1Regex.test(body)) {\n const paragraphs = body.split(/\\n{2,}/).filter((p) => {\n const trimmed = p.trim();\n return trimmed.length > 0 && !trimmed.startsWith('<!--') && !trimmed.startsWith('#');\n });\n if (paragraphs.length > 0) {\n const para = paragraphs[0].replace(/\\n/g, ' ').trim();\n const l1 = para.length > 300 ? para.slice(0, 297) + '...' : para;\n const l0Pos = body.indexOf('-->');\n if (l0Pos !== -1) {\n const insertPos = l0Pos + 3;\n body = body.slice(0, insertPos) + `\\n<!-- L1: ${l1} -->` + body.slice(insertPos);\n } else {\n body = `<!-- L1: ${l1} -->\\n${body}`;\n }\n fixes.push('Generated L1 from first paragraph');\n }\n }\n\n const result = matter.stringify(body, data);\n const outFilename = ensureMdExtension(filename);\n return { content: result, filename: outFilename, fixes };\n}\n\n/**\n * Wrap a bash hook script in harness markdown.\n */\nfunction normalizeBashHook(\n content: string,\n filename: string,\n type: string | null,\n options: UniversalInstallOptions | undefined,\n fixes: string[],\n): { content: string; filename: string; fixes: string[] } {\n const id = options?.id ?? deriveId(filename);\n const primitiveType = type ?? 'workflow';\n const tags = [primitiveType, 'hook', ...(options?.tags ?? [])];\n\n // Extract description from comments at top of script\n const commentLines = content.split('\\n')\n .filter((line) => line.startsWith('#') && !line.startsWith('#!'))\n .map((line) => line.replace(/^#\\s?/, '').trim())\n .filter((line) => line.length > 0);\n\n const description = commentLines.length > 0\n ? commentLines.slice(0, 3).join(' ')\n : `Bash hook: ${id}`;\n\n const frontmatter: Record<string, unknown> = {\n id,\n created: new Date().toISOString().split('T')[0],\n author: 'human',\n status: 'active',\n tags: [...new Set(tags)],\n };\n\n const l0 = description.length > 120 ? description.slice(0, 117) + '...' : description;\n\n let body = `<!-- L0: ${l0} -->\\n\\n`;\n body += `# ${id}\\n\\n`;\n body += `${description}\\n\\n`;\n body += '```bash\\n';\n body += content;\n if (!content.endsWith('\\n')) body += '\\n';\n body += '```\\n';\n\n const result = matter.stringify(body, frontmatter);\n fixes.push('Wrapped bash script in harness markdown');\n fixes.push(`Added frontmatter (id: ${id}, type: ${primitiveType})`);\n\n const outFilename = deriveId(filename) + '.md';\n return { content: result, filename: outFilename, fixes };\n}\n\n/**\n * Convert an MCP config to harness tool documentation.\n */\nfunction normalizeMcpConfig(\n content: string,\n filename: string,\n options: UniversalInstallOptions | undefined,\n fixes: string[],\n): { content: string; filename: string; fixes: string[] } {\n const id = options?.id ?? deriveId(filename);\n const tags = ['tool', 'mcp', ...(options?.tags ?? [])];\n\n // Try to parse config\n let config: Record<string, unknown> = {};\n const ext = extname(filename).toLowerCase();\n try {\n if (ext === '.json') {\n config = JSON.parse(content) as Record<string, unknown>;\n } else {\n config = parseYaml(content) as Record<string, unknown>;\n }\n } catch {\n fixes.push('Failed to parse MCP config');\n }\n\n const serverName = String(config.name ?? config.command ?? id);\n const description = String(config.description ?? `MCP server: ${serverName}`);\n\n const frontmatter: Record<string, unknown> = {\n id,\n created: new Date().toISOString().split('T')[0],\n author: 'human',\n status: 'active',\n tags: [...new Set(tags)],\n };\n\n const l0 = description.length > 120 ? description.slice(0, 117) + '...' : description;\n\n let body = `<!-- L0: ${l0} -->\\n\\n`;\n body += `# MCP Server: ${serverName}\\n\\n`;\n body += `${description}\\n\\n`;\n body += '## Configuration\\n\\n';\n body += '```json\\n';\n body += JSON.stringify(config, null, 2);\n body += '\\n```\\n';\n\n const result = matter.stringify(body, frontmatter);\n fixes.push('Converted MCP config to harness tool documentation');\n fixes.push(`Added frontmatter (id: ${id})`);\n\n const outFilename = deriveId(filename) + '.md';\n return { content: result, filename: outFilename, fixes };\n}\n\n// ─── Source Resolution ───────────────────────────────────────────────────────\n\n/**\n * Resolve a source reference to a local file path.\n *\n * Supports:\n * - Local file paths (absolute or relative)\n * - HTTPS URLs (GitHub raw, any markdown URL)\n * - Source query (searches registered sources)\n *\n * @returns Path to a local file (downloaded if remote)\n */\nexport async function resolveSource(\n source: string,\n harnessDir: string,\n): Promise<{ localPath: string; originalSource: string; error?: string }> {\n // Case 1: Local file path\n if (existsSync(source)) {\n return { localPath: source, originalSource: source };\n }\n\n // Case 2: URL\n if (source.startsWith('https://') || source.startsWith('http://')) {\n // Convert GitHub URL to raw if needed\n const rawUrl = convertToRawUrl(source);\n const result = await downloadCapability(rawUrl);\n if (result.downloaded) {\n return { localPath: result.localPath, originalSource: source };\n }\n return { localPath: '', originalSource: source, error: result.error };\n }\n\n // Case 3: Source registry lookup — search known sources\n const results = discoverSources(harnessDir, source, { maxResults: 1 });\n if (results.length > 0) {\n const hit = results[0];\n // If the source is a GitHub source, construct a raw URL\n if (hit.source.type === 'github') {\n const rawUrl = convertToRawUrl(hit.url);\n const result = await downloadCapability(rawUrl);\n if (result.downloaded) {\n return { localPath: result.localPath, originalSource: source };\n }\n return { localPath: '', originalSource: source, error: result.error };\n }\n return { localPath: '', originalSource: source, error: `Source \"${hit.source.name}\" is type \"${hit.source.type}\" — direct install not yet supported for this type` };\n }\n\n return { localPath: '', originalSource: source, error: `Could not resolve \"${source}\" — not a local file, URL, or known source` };\n}\n\n// ─── Main Install Function ───────────────────────────────────────────────────\n\n/**\n * Universal install: resolve → detect → normalize → fix → install.\n *\n * Accepts a local path, URL, or search query. Detects the format,\n * normalizes to harness convention, applies auto-fixes, and installs\n * to the correct directory.\n *\n * @param harnessDir - Harness directory\n * @param source - File path, URL, or name to install\n * @param options - Installation options\n * @returns Install result with status, fixes, errors, dependency hints\n */\nexport async function universalInstall(\n harnessDir: string,\n source: string,\n options?: UniversalInstallOptions,\n): Promise<UniversalInstallResult> {\n const result: UniversalInstallResult = {\n installed: false,\n source,\n format: { format: 'unknown', primitiveType: null, confidence: 0, reasons: [] },\n destination: '',\n fixes: [],\n errors: [],\n suggestedDependencies: [],\n };\n\n // Step 1: Resolve source to local file\n const resolved = await resolveSource(source, harnessDir);\n if (resolved.error || !resolved.localPath) {\n result.errors.push(resolved.error ?? 'Failed to resolve source');\n return result;\n }\n\n // Step 2: Read content\n let content: string;\n try {\n content = readFileSync(resolved.localPath, 'utf-8');\n } catch (err) {\n result.errors.push(`Failed to read file: ${err instanceof Error ? err.message : String(err)}`);\n return result;\n }\n\n if (content.trim().length === 0) {\n result.errors.push('File is empty');\n return result;\n }\n\n // Step 3: Detect format\n const filename = basename(resolved.localPath);\n const detection = detectFormat(content, filename);\n result.format = detection;\n\n // Step 4: Normalize to harness convention\n const normalized = normalizeToHarness(content, filename, detection, options);\n result.fixes.push(...normalized.fixes);\n\n // Step 4b: Record provenance for URL installs so every installed file is\n // traceable back to its source. Local-path installs are skipped — the path\n // on disk is not a stable identifier.\n let finalContent = normalized.content;\n if (source.startsWith('http://') || source.startsWith('https://')) {\n finalContent = await recordProvenance(finalContent, source);\n result.fixes.push('Recorded provenance (source, installed_at, installed_by)');\n\n // Step 4c: License policy enforcement (Level 3 of task 12.14).\n // The license fields are now in the frontmatter from recordProvenance.\n // Re-parse them, evaluate against the user's `install:` config policy,\n // and either continue, warn, prompt, or block based on the decision.\n //\n // Loaded inline so universalInstall stays decoupled from CLI lifecycle.\n // Failures to load config fall back to safe defaults — never crash.\n try {\n const { loadConfig } = await import('../core/config.js');\n const config = loadConfig(harnessDir);\n const installPolicy = config.install;\n const parsed = matter(finalContent);\n const detected: LicenseInfo = {\n spdxId: typeof parsed.data.license === 'string' ? parsed.data.license : 'UNKNOWN',\n copyright: typeof parsed.data.copyright === 'string' ? parsed.data.copyright : undefined,\n licenseSource:\n typeof parsed.data.license_source === 'string' ? parsed.data.license_source : undefined,\n };\n\n const decision = evaluateLicensePolicy(detected, installPolicy, options?.forceLicense);\n\n if (decision.action === 'block') {\n result.errors.push(\n `License policy blocked install: ${decision.reason}` +\n (detected.licenseSource ? ` (${detected.licenseSource})` : '') +\n `. To override, re-run with --force-license <SPDX> if you have written permission.`,\n );\n return result;\n }\n\n if (decision.action === 'warn') {\n log.warn(\n `[install] license policy warning: ${decision.reason}. ` +\n `Installing anyway (set install.on_unknown_license: block in config.yaml to refuse).`,\n );\n result.fixes.push(`License policy: warned (${decision.spdxId})`);\n }\n\n if (decision.action === 'prompt') {\n // Prompt only on TTY. In non-TTY (CI, piped input), default to BLOCK\n // — safer than silently installing unknown content in automation.\n if (process.stdin.isTTY && process.stdout.isTTY) {\n const readline = await import('readline');\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const question = `\\n ⚠ License policy: ${decision.reason}\\n ${\n detected.licenseSource ? `License source: ${detected.licenseSource}\\n ` : ''\n }Install anyway? [y/N] `;\n const answer = await new Promise<string>((res) => rl.question(question, res));\n rl.close();\n if (!/^y(es)?$/i.test(answer.trim())) {\n result.errors.push('License policy prompt declined by user');\n return result;\n }\n result.fixes.push(`License policy: prompted, user accepted (${decision.spdxId})`);\n } else {\n result.errors.push(\n `License policy requires interactive confirmation (${decision.reason}) ` +\n `but stdin/stdout is not a TTY. Re-run interactively, or pass ` +\n `--force-license <SPDX> if you have written permission.`,\n );\n return result;\n }\n }\n\n // 'allow' falls through silently.\n if (decision.action === 'allow' && options?.forceLicense) {\n result.fixes.push(`License policy: forced to ${decision.spdxId}`);\n }\n } catch (err) {\n // Config load failure should not block install. Log for visibility.\n log.warn(\n `[install] license policy check skipped: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n // Step 5: Write normalized content to temp file for installation\n const tempDir = join(tmpdir(), 'harness-install');\n mkdirSync(tempDir, { recursive: true });\n const tempPath = join(tempDir, normalized.filename);\n writeFileSync(tempPath, finalContent, 'utf-8');\n\n // Step 6: Apply auto-fix if not skipped\n if (!options?.skipFix) {\n const fixResult = fixCapability(tempPath);\n result.fixes.push(...fixResult.fixes_applied);\n\n if (!fixResult.valid && !options?.force) {\n result.errors.push(...fixResult.errors);\n return result;\n }\n }\n\n // Step 7: Install via existing pipeline\n const installResult = installCapability(harnessDir, tempPath);\n result.installed = installResult.installed;\n result.destination = installResult.destination;\n\n if (!installResult.installed) {\n result.errors.push(...installResult.evalResult.errors);\n // If force mode, try direct copy\n if (options?.force && detection.primitiveType) {\n const targetDir = join(harnessDir, TYPE_DIRS[detection.primitiveType] ?? 'skills');\n if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });\n const dest = join(targetDir, normalized.filename);\n copyFileSync(tempPath, dest);\n result.installed = true;\n result.destination = dest;\n result.fixes.push('Force-installed despite validation errors');\n }\n }\n\n // Step 8: Scan for dependency hints\n result.suggestedDependencies = extractDependencyHints(normalized.content);\n\n return result;\n}\n\n/**\n * Install from a URL (convenience wrapper).\n */\nexport async function installFromUrl(\n harnessDir: string,\n url: string,\n options?: UniversalInstallOptions,\n): Promise<UniversalInstallResult> {\n return universalInstall(harnessDir, url, options);\n}\n\n/**\n * Install from a local file path (convenience wrapper).\n */\nexport async function installFromFile(\n harnessDir: string,\n filePath: string,\n options?: UniversalInstallOptions,\n): Promise<UniversalInstallResult> {\n return universalInstall(harnessDir, filePath, options);\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction deriveId(filename: string): string {\n const base = basename(filename).replace(/\\.(md|faf|yaml|yml|json|sh|bash)$/i, '');\n return base.replace(/[^a-z0-9-]/gi, '-').toLowerCase();\n}\n\nfunction ensureMdExtension(filename: string): string {\n if (filename.endsWith('.md')) return filename;\n return deriveId(filename) + '.md';\n}\n\n/**\n * Convert a GitHub URL to its raw content URL.\n *\n * Handles:\n * - github.com/owner/repo/blob/branch/path → raw.githubusercontent.com/owner/repo/branch/path\n * - Already raw.githubusercontent.com URLs → pass through\n * - Other URLs → pass through\n */\nexport function convertToRawUrl(url: string): string {\n // Already raw\n if (url.includes('raw.githubusercontent.com')) return url;\n\n // GitHub blob URL → raw\n const blobMatch = url.match(\n /^https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)\\/blob\\/(.+)$/,\n );\n if (blobMatch) {\n const [, owner, repo, rest] = blobMatch;\n return `https://raw.githubusercontent.com/${owner}/${repo}/${rest}`;\n }\n\n return url;\n}\n\n/**\n * Detect if content matches Claude Code SKILL.md patterns.\n * Claude skills are plain markdown with specific structural patterns.\n */\nfunction detectClaudeSkillPattern(content: string, filename: string): boolean {\n const nameLower = filename.toLowerCase();\n\n // Filename patterns\n if (nameLower === 'skill.md' || nameLower.endsWith('-skill.md') || nameLower.endsWith('_skill.md')) {\n return true;\n }\n\n // Content patterns common in Claude Code skills\n const patterns = [\n /^#\\s+.+skill/im,\n /instructions?\\s+for\\s+/i,\n /when\\s+(the\\s+)?user\\s+(asks?|wants?|needs?|requests?)/i,\n /you\\s+(should|must|will)\\s+/i,\n ];\n\n let matches = 0;\n for (const pattern of patterns) {\n if (pattern.test(content)) matches++;\n }\n\n // Need at least 2 pattern matches to classify as Claude skill\n // (plain markdown + instructional tone)\n return matches >= 2 && !content.startsWith('---');\n}\n\nfunction inferTypeFromFafType(fafType: string): string | null {\n const typeMap: Record<string, string> = {\n skill: 'skill',\n agent: 'agent',\n rule: 'rule',\n playbook: 'playbook',\n workflow: 'workflow',\n tool: 'tool',\n instinct: 'instinct',\n hook: 'workflow',\n template: 'skill',\n plugin: 'skill',\n };\n\n return typeMap[fafType.toLowerCase()] ?? null;\n}\n\nfunction inferTypeFromContent(content: string, filename: string): string | null {\n const lower = content.toLowerCase();\n const nameLower = filename.toLowerCase();\n\n // From filename\n if (nameLower.includes('rule')) return 'rule';\n if (nameLower.includes('agent')) return 'agent';\n if (nameLower.includes('playbook')) return 'playbook';\n if (nameLower.includes('workflow')) return 'workflow';\n if (nameLower.includes('instinct')) return 'instinct';\n if (nameLower.includes('tool')) return 'tool';\n if (nameLower.includes('skill')) return 'skill';\n\n // From content patterns\n if (lower.includes('# rule:') || lower.includes('## rules')) return 'rule';\n if (lower.includes('# agent:') || lower.includes('## agent')) return 'agent';\n if (lower.includes('# playbook:') || lower.includes('## playbook')) return 'playbook';\n if (lower.includes('# skill:') || lower.includes('## skill')) return 'skill';\n if (lower.includes('# workflow:') || lower.includes('## workflow')) return 'workflow';\n if (lower.includes('# tool:') || lower.includes('## tool')) return 'tool';\n\n // Default for markdown without clear type\n return null;\n}\n\n/**\n * Extract dependency hints from content.\n * Looks for references to tools, skills, or other primitives.\n */\nfunction extractDependencyHints(content: string): string[] {\n const hints: string[] = [];\n const seen = new Set<string>();\n\n // Look for \"requires:\" or \"depends:\" in frontmatter\n try {\n const parsed = matter(content);\n const data = parsed.data as Record<string, unknown>;\n if (Array.isArray(data.requires)) {\n for (const dep of data.requires as string[]) {\n if (!seen.has(dep)) {\n hints.push(dep);\n seen.add(dep);\n }\n }\n }\n if (Array.isArray(data.depends)) {\n for (const dep of data.depends as string[]) {\n if (!seen.has(dep)) {\n hints.push(dep);\n seen.add(dep);\n }\n }\n }\n if (Array.isArray(data.related)) {\n for (const dep of data.related as string[]) {\n if (!seen.has(dep)) {\n hints.push(dep);\n seen.add(dep);\n }\n }\n }\n } catch {\n // Ignore parse errors\n }\n\n return hints;\n}\n"],"mappings":"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","names":["require"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
doctorHarness,
|
|
5
|
+
validateHarness
|
|
6
|
+
} from "./chunk-AN6Y4MDD.js";
|
|
7
|
+
import "./chunk-UXCHAS3Z.js";
|
|
8
|
+
import "./chunk-5H34JPMB.js";
|
|
9
|
+
import "./chunk-D7AWV24Z.js";
|
|
10
|
+
import "./chunk-7GZ4D6V6.js";
|
|
11
|
+
import "./chunk-UDZIS2AQ.js";
|
|
12
|
+
import "./chunk-Z2PUCXTZ.js";
|
|
13
|
+
import "./chunk-2UVWCTAY.js";
|
|
14
|
+
import "./chunk-BSKDOFRT.js";
|
|
15
|
+
import "./chunk-EC42HQQH.js";
|
|
16
|
+
import "./chunk-4TQQZILG.js";
|
|
17
|
+
export {
|
|
18
|
+
doctorHarness,
|
|
19
|
+
validateHarness
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=validator-LM7RZWSH.js.map
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
loadDirectory
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-ZZJOFKAT.js";
|
|
5
|
+
} from "./chunk-2UVWCTAY.js";
|
|
6
|
+
import "./chunk-4TQQZILG.js";
|
|
8
7
|
|
|
9
8
|
// src/runtime/verification-gate.ts
|
|
10
9
|
import { existsSync } from "fs";
|
|
@@ -243,4 +242,4 @@ export {
|
|
|
243
242
|
getGatesForPlaybook,
|
|
244
243
|
loadGates
|
|
245
244
|
};
|
|
246
|
-
//# sourceMappingURL=verification-gate-
|
|
245
|
+
//# sourceMappingURL=verification-gate-2O6DF2B7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime/verification-gate.ts"],"sourcesContent":["import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { loadDirectory } from '../primitives/loader.js';\nimport type { HarnessDocument } from '../core/types.js';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface VerificationCriterion {\n /** Human-readable criterion description */\n description: string;\n /** Whether this criterion must be manually checked (vs auto-checkable) */\n manual: boolean;\n /** Optional command to run for automated verification */\n command?: string;\n /** Optional expected output pattern (regex) for automated verification */\n expectedPattern?: string;\n}\n\nexport interface VerificationGate {\n /** Gate ID derived from playbook/workflow stage */\n id: string;\n /** Stage name this gate guards (e.g., \"Build\", \"Verify\") */\n stage: string;\n /** Source playbook/workflow ID */\n sourceId: string;\n /** Acceptance criteria that must pass before proceeding */\n criteria: VerificationCriterion[];\n}\n\nexport interface GateCheckResult {\n gateId: string;\n stage: string;\n passed: boolean;\n /** Individual criterion results */\n results: Array<{\n criterion: string;\n passed: boolean;\n detail?: string;\n }>;\n /** Criteria that need manual verification */\n pendingManual: string[];\n}\n\nexport interface GateExtractResult {\n gates: VerificationGate[];\n /** Source playbook/workflow IDs that had gates */\n sources: string[];\n}\n\n// ─── Gate Extraction ─────────────────────────────────────────────────────────\n\n/**\n * Extract verification gates from a playbook or workflow document.\n *\n * Gates are detected from:\n * 1. `## Gate:` or `## Verification:` sections in the document body\n * 2. Acceptance criteria blocks (`### Acceptance Criteria` or `### Gate`)\n * 3. Inline gate markers: `<!-- gate: description -->` in the markdown\n * 4. Numbered steps with `[x]` checkbox markers (treated as manual criteria)\n */\nexport function extractGates(doc: HarnessDocument): VerificationGate[] {\n const gates: VerificationGate[] = [];\n const body = doc.body;\n\n // Strategy 1: Named gate sections\n const gateSectionRegex = /##\\s+(?:Gate|Verification):\\s*(.+)\\n([\\s\\S]*?)(?=\\n##\\s|\\n*$)/gi;\n let match: RegExpExecArray | null;\n\n while ((match = gateSectionRegex.exec(body)) !== null) {\n const stageName = match[1].trim();\n const sectionBody = match[2];\n const criteria = extractCriteriaFromSection(sectionBody);\n\n if (criteria.length > 0) {\n gates.push({\n id: `${doc.frontmatter.id}:${slugify(stageName)}`,\n stage: stageName,\n sourceId: doc.frontmatter.id,\n criteria,\n });\n }\n }\n\n // Strategy 2: Acceptance criteria subsections\n const acRegex = /###\\s+(?:Acceptance Criteria|Gate)\\s*(?::\\s*(.+))?\\n([\\s\\S]*?)(?=\\n###?\\s|\\n*$)/gi;\n\n while ((match = acRegex.exec(body)) !== null) {\n const stageName = match[1]?.trim() ?? 'default';\n const sectionBody = match[2];\n const criteria = extractCriteriaFromSection(sectionBody);\n\n if (criteria.length > 0) {\n const gateId = `${doc.frontmatter.id}:ac-${slugify(stageName)}`;\n // Avoid duplicates from strategy 1\n if (!gates.some((g) => g.id === gateId)) {\n gates.push({\n id: gateId,\n stage: stageName,\n sourceId: doc.frontmatter.id,\n criteria,\n });\n }\n }\n }\n\n // Strategy 3: Inline gate markers\n const inlineRegex = /<!--\\s*gate:\\s*(.+?)\\s*-->/gi;\n\n while ((match = inlineRegex.exec(body)) !== null) {\n const desc = match[1].trim();\n const gateId = `${doc.frontmatter.id}:inline-${slugify(desc.slice(0, 40))}`;\n\n if (!gates.some((g) => g.id === gateId)) {\n gates.push({\n id: gateId,\n stage: 'inline',\n sourceId: doc.frontmatter.id,\n criteria: [{\n description: desc,\n manual: !desc.includes('`'),\n command: extractInlineCommand(desc),\n }],\n });\n }\n }\n\n // Strategy 4: Steps with checkboxes between stages\n const stepGates = extractStepGates(doc);\n for (const gate of stepGates) {\n if (!gates.some((g) => g.id === gate.id)) {\n gates.push(gate);\n }\n }\n\n return gates;\n}\n\n/**\n * Extract criteria from a section body (bullet list parsing).\n */\nfunction extractCriteriaFromSection(section: string): VerificationCriterion[] {\n const criteria: VerificationCriterion[] = [];\n\n for (const line of section.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n // Bullet or numbered list items\n const listMatch = trimmed.match(/^[-*]\\s+(?:\\[[ x]]\\s+)?(.+)/);\n if (!listMatch) continue;\n\n const desc = listMatch[1].trim();\n if (!desc) continue;\n\n // Check if it's an automated criterion (contains backtick command)\n const cmdMatch = desc.match(/`(.+?)`/);\n const isManual = !cmdMatch;\n\n // Check for expected pattern: \"should output X\" or \"expected: X\"\n const patternMatch = desc.match(/(?:should\\s+(?:output|return|show)|expected:\\s*)(.+?)(?:\\)|$)/i);\n\n criteria.push({\n description: desc,\n manual: isManual,\n command: cmdMatch ? cmdMatch[1] : undefined,\n expectedPattern: patternMatch ? patternMatch[1].trim() : undefined,\n });\n }\n\n return criteria;\n}\n\n/**\n * Extract verification gates from step transitions.\n * Looks for numbered steps where intermediate verification steps exist.\n */\nfunction extractStepGates(doc: HarnessDocument): VerificationGate[] {\n const gates: VerificationGate[] = [];\n const lines = doc.body.split('\\n');\n let currentStep = '';\n let criteriaBuffer: VerificationCriterion[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Detect numbered step headers (e.g., \"1. **Understand**\" or \"## Step 1:\")\n const stepMatch = trimmed.match(/^(?:\\d+\\.\\s+\\*\\*(.+?)\\*\\*|##\\s+Step\\s+\\d+:\\s*(.+))/);\n if (stepMatch) {\n // Flush previous gate if criteria were collected\n if (currentStep && criteriaBuffer.length > 0) {\n gates.push({\n id: `${doc.frontmatter.id}:step-${slugify(currentStep)}`,\n stage: currentStep,\n sourceId: doc.frontmatter.id,\n criteria: [...criteriaBuffer],\n });\n criteriaBuffer = [];\n }\n currentStep = (stepMatch[1] ?? stepMatch[2]).trim();\n continue;\n }\n\n // Collect checkbox items within steps as criteria\n const checkboxMatch = trimmed.match(/^[-*]\\s+\\[[ x]]\\s+(.+)/);\n if (checkboxMatch && currentStep) {\n const desc = checkboxMatch[1].trim();\n const cmdMatch = desc.match(/`(.+?)`/);\n criteriaBuffer.push({\n description: desc,\n manual: !cmdMatch,\n command: cmdMatch ? cmdMatch[1] : undefined,\n });\n }\n }\n\n // Flush last step\n if (currentStep && criteriaBuffer.length > 0) {\n gates.push({\n id: `${doc.frontmatter.id}:step-${slugify(currentStep)}`,\n stage: currentStep,\n sourceId: doc.frontmatter.id,\n criteria: [...criteriaBuffer],\n });\n }\n\n return gates;\n}\n\n/**\n * Extract a command from an inline gate description (text between backticks).\n */\nfunction extractInlineCommand(desc: string): string | undefined {\n const match = desc.match(/`(.+?)`/);\n return match ? match[1] : undefined;\n}\n\n/**\n * Convert text to a URL-safe slug.\n */\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .slice(0, 50)\n .replace(/-+$/, '');\n}\n\n// ─── Gate Loading ────────────────────────────────────────────────────────────\n\n/**\n * Load all verification gates from playbooks and workflows in the harness.\n */\nexport function loadGates(harnessDir: string): GateExtractResult {\n const gates: VerificationGate[] = [];\n const sources: string[] = [];\n\n for (const dir of ['playbooks', 'workflows']) {\n const fullPath = join(harnessDir, dir);\n if (!existsSync(fullPath)) continue;\n\n const docs = loadDirectory(fullPath);\n for (const doc of docs) {\n if (doc.frontmatter.status !== 'active') continue;\n const docGates = extractGates(doc);\n if (docGates.length > 0) {\n gates.push(...docGates);\n if (!sources.includes(doc.frontmatter.id)) {\n sources.push(doc.frontmatter.id);\n }\n }\n }\n }\n\n return { gates, sources };\n}\n\n/**\n * Find gates for a specific playbook/workflow by ID.\n */\nexport function getGatesForPlaybook(harnessDir: string, playbookId: string): VerificationGate[] {\n const { gates } = loadGates(harnessDir);\n return gates.filter((g) => g.sourceId === playbookId);\n}\n\n// ─── Gate Checking ───────────────────────────────────────────────────────────\n\n/**\n * Check a verification gate against provided results.\n * For manual criteria, checks against the `manualResults` map.\n * For automated criteria, checks if the command output matches the expected pattern.\n *\n * @param gate - The gate to check\n * @param manualResults - Map of criterion description → pass/fail\n * @param commandOutputs - Map of command → actual output (for automated checks)\n */\nexport function checkGate(\n gate: VerificationGate,\n manualResults?: Map<string, boolean>,\n commandOutputs?: Map<string, string>,\n): GateCheckResult {\n const results: Array<{ criterion: string; passed: boolean; detail?: string }> = [];\n const pendingManual: string[] = [];\n\n for (const criterion of gate.criteria) {\n if (criterion.manual) {\n // Manual criterion — check if result was provided\n if (manualResults && manualResults.has(criterion.description)) {\n const passed = manualResults.get(criterion.description)!;\n results.push({\n criterion: criterion.description,\n passed,\n detail: passed ? 'Manually verified' : 'Manual verification failed',\n });\n } else {\n pendingManual.push(criterion.description);\n results.push({\n criterion: criterion.description,\n passed: false,\n detail: 'Awaiting manual verification',\n });\n }\n } else if (criterion.command && commandOutputs) {\n // Automated criterion — check command output\n const output = commandOutputs.get(criterion.command);\n if (output === undefined) {\n results.push({\n criterion: criterion.description,\n passed: false,\n detail: `Command not executed: ${criterion.command}`,\n });\n } else if (criterion.expectedPattern) {\n try {\n const regex = new RegExp(criterion.expectedPattern, 'i');\n const passed = regex.test(output);\n results.push({\n criterion: criterion.description,\n passed,\n detail: passed\n ? `Output matches expected pattern`\n : `Output does not match expected pattern: ${criterion.expectedPattern}`,\n });\n } catch {\n // Invalid regex — treat as string match\n const passed = output.includes(criterion.expectedPattern);\n results.push({\n criterion: criterion.description,\n passed,\n detail: passed ? 'Output contains expected text' : 'Output missing expected text',\n });\n }\n } else {\n // No expected pattern — command succeeded if output exists\n results.push({\n criterion: criterion.description,\n passed: true,\n detail: 'Command produced output',\n });\n }\n } else {\n // Automated criterion but no output available\n results.push({\n criterion: criterion.description,\n passed: false,\n detail: criterion.command ? `Command not executed: ${criterion.command}` : 'No verification method',\n });\n }\n }\n\n const passed = results.every((r) => r.passed) && pendingManual.length === 0;\n\n return {\n gateId: gate.id,\n stage: gate.stage,\n passed,\n results,\n pendingManual,\n };\n}\n\n/**\n * Check all gates for a playbook/workflow. Returns individual gate results.\n */\nexport function checkAllGates(\n harnessDir: string,\n playbookId: string,\n manualResults?: Map<string, boolean>,\n commandOutputs?: Map<string, string>,\n): GateCheckResult[] {\n const gates = getGatesForPlaybook(harnessDir, playbookId);\n return gates.map((gate) => checkGate(gate, manualResults, commandOutputs));\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AA2Dd,SAAS,aAAa,KAA0C;AACrE,QAAM,QAA4B,CAAC;AACnC,QAAM,OAAO,IAAI;AAGjB,QAAM,mBAAmB;AACzB,MAAI;AAEJ,UAAQ,QAAQ,iBAAiB,KAAK,IAAI,OAAO,MAAM;AACrD,UAAM,YAAY,MAAM,CAAC,EAAE,KAAK;AAChC,UAAM,cAAc,MAAM,CAAC;AAC3B,UAAM,WAAW,2BAA2B,WAAW;AAEvD,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,KAAK;AAAA,QACT,IAAI,GAAG,IAAI,YAAY,EAAE,IAAI,QAAQ,SAAS,CAAC;AAAA,QAC/C,OAAO;AAAA,QACP,UAAU,IAAI,YAAY;AAAA,QAC1B;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,UAAU;AAEhB,UAAQ,QAAQ,QAAQ,KAAK,IAAI,OAAO,MAAM;AAC5C,UAAM,YAAY,MAAM,CAAC,GAAG,KAAK,KAAK;AACtC,UAAM,cAAc,MAAM,CAAC;AAC3B,UAAM,WAAW,2BAA2B,WAAW;AAEvD,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,SAAS,GAAG,IAAI,YAAY,EAAE,OAAO,QAAQ,SAAS,CAAC;AAE7D,UAAI,CAAC,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,GAAG;AACvC,cAAM,KAAK;AAAA,UACT,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,UAAU,IAAI,YAAY;AAAA,UAC1B;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,QAAM,cAAc;AAEpB,UAAQ,QAAQ,YAAY,KAAK,IAAI,OAAO,MAAM;AAChD,UAAM,OAAO,MAAM,CAAC,EAAE,KAAK;AAC3B,UAAM,SAAS,GAAG,IAAI,YAAY,EAAE,WAAW,QAAQ,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC;AAEzE,QAAI,CAAC,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,GAAG;AACvC,YAAM,KAAK;AAAA,QACT,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,UAAU,IAAI,YAAY;AAAA,QAC1B,UAAU,CAAC;AAAA,UACT,aAAa;AAAA,UACb,QAAQ,CAAC,KAAK,SAAS,GAAG;AAAA,UAC1B,SAAS,qBAAqB,IAAI;AAAA,QACpC,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,YAAY,iBAAiB,GAAG;AACtC,aAAW,QAAQ,WAAW;AAC5B,QAAI,CAAC,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,EAAE,GAAG;AACxC,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,2BAA2B,SAA0C;AAC5E,QAAM,WAAoC,CAAC;AAE3C,aAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AAGd,UAAM,YAAY,QAAQ,MAAM,6BAA6B;AAC7D,QAAI,CAAC,UAAW;AAEhB,UAAM,OAAO,UAAU,CAAC,EAAE,KAAK;AAC/B,QAAI,CAAC,KAAM;AAGX,UAAM,WAAW,KAAK,MAAM,SAAS;AACrC,UAAM,WAAW,CAAC;AAGlB,UAAM,eAAe,KAAK,MAAM,gEAAgE;AAEhG,aAAS,KAAK;AAAA,MACZ,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS,WAAW,SAAS,CAAC,IAAI;AAAA,MAClC,iBAAiB,eAAe,aAAa,CAAC,EAAE,KAAK,IAAI;AAAA,IAC3D,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAMA,SAAS,iBAAiB,KAA0C;AAClE,QAAM,QAA4B,CAAC;AACnC,QAAM,QAAQ,IAAI,KAAK,MAAM,IAAI;AACjC,MAAI,cAAc;AAClB,MAAI,iBAA0C,CAAC;AAE/C,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAG1B,UAAM,YAAY,QAAQ,MAAM,oDAAoD;AACpF,QAAI,WAAW;AAEb,UAAI,eAAe,eAAe,SAAS,GAAG;AAC5C,cAAM,KAAK;AAAA,UACT,IAAI,GAAG,IAAI,YAAY,EAAE,SAAS,QAAQ,WAAW,CAAC;AAAA,UACtD,OAAO;AAAA,UACP,UAAU,IAAI,YAAY;AAAA,UAC1B,UAAU,CAAC,GAAG,cAAc;AAAA,QAC9B,CAAC;AACD,yBAAiB,CAAC;AAAA,MACpB;AACA,qBAAe,UAAU,CAAC,KAAK,UAAU,CAAC,GAAG,KAAK;AAClD;AAAA,IACF;AAGA,UAAM,gBAAgB,QAAQ,MAAM,wBAAwB;AAC5D,QAAI,iBAAiB,aAAa;AAChC,YAAM,OAAO,cAAc,CAAC,EAAE,KAAK;AACnC,YAAM,WAAW,KAAK,MAAM,SAAS;AACrC,qBAAe,KAAK;AAAA,QAClB,aAAa;AAAA,QACb,QAAQ,CAAC;AAAA,QACT,SAAS,WAAW,SAAS,CAAC,IAAI;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AAGA,MAAI,eAAe,eAAe,SAAS,GAAG;AAC5C,UAAM,KAAK;AAAA,MACT,IAAI,GAAG,IAAI,YAAY,EAAE,SAAS,QAAQ,WAAW,CAAC;AAAA,MACtD,OAAO;AAAA,MACP,UAAU,IAAI,YAAY;AAAA,MAC1B,UAAU,CAAC,GAAG,cAAc;AAAA,IAC9B,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAKA,SAAS,qBAAqB,MAAkC;AAC9D,QAAM,QAAQ,KAAK,MAAM,SAAS;AAClC,SAAO,QAAQ,MAAM,CAAC,IAAI;AAC5B;AAKA,SAAS,QAAQ,MAAsB;AACrC,SAAO,KACJ,YAAY,EACZ,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,QAAQ,GAAG,EACnB,MAAM,GAAG,EAAE,EACX,QAAQ,OAAO,EAAE;AACtB;AAOO,SAAS,UAAU,YAAuC;AAC/D,QAAM,QAA4B,CAAC;AACnC,QAAM,UAAoB,CAAC;AAE3B,aAAW,OAAO,CAAC,aAAa,WAAW,GAAG;AAC5C,UAAM,WAAW,KAAK,YAAY,GAAG;AACrC,QAAI,CAAC,WAAW,QAAQ,EAAG;AAE3B,UAAM,OAAO,cAAc,QAAQ;AACnC,eAAW,OAAO,MAAM;AACtB,UAAI,IAAI,YAAY,WAAW,SAAU;AACzC,YAAM,WAAW,aAAa,GAAG;AACjC,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,KAAK,GAAG,QAAQ;AACtB,YAAI,CAAC,QAAQ,SAAS,IAAI,YAAY,EAAE,GAAG;AACzC,kBAAQ,KAAK,IAAI,YAAY,EAAE;AAAA,QACjC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,QAAQ;AAC1B;AAKO,SAAS,oBAAoB,YAAoB,YAAwC;AAC9F,QAAM,EAAE,MAAM,IAAI,UAAU,UAAU;AACtC,SAAO,MAAM,OAAO,CAAC,MAAM,EAAE,aAAa,UAAU;AACtD;AAaO,SAAS,UACd,MACA,eACA,gBACiB;AACjB,QAAM,UAA0E,CAAC;AACjF,QAAM,gBAA0B,CAAC;AAEjC,aAAW,aAAa,KAAK,UAAU;AACrC,QAAI,UAAU,QAAQ;AAEpB,UAAI,iBAAiB,cAAc,IAAI,UAAU,WAAW,GAAG;AAC7D,cAAMA,UAAS,cAAc,IAAI,UAAU,WAAW;AACtD,gBAAQ,KAAK;AAAA,UACX,WAAW,UAAU;AAAA,UACrB,QAAAA;AAAA,UACA,QAAQA,UAAS,sBAAsB;AAAA,QACzC,CAAC;AAAA,MACH,OAAO;AACL,sBAAc,KAAK,UAAU,WAAW;AACxC,gBAAQ,KAAK;AAAA,UACX,WAAW,UAAU;AAAA,UACrB,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF,WAAW,UAAU,WAAW,gBAAgB;AAE9C,YAAM,SAAS,eAAe,IAAI,UAAU,OAAO;AACnD,UAAI,WAAW,QAAW;AACxB,gBAAQ,KAAK;AAAA,UACX,WAAW,UAAU;AAAA,UACrB,QAAQ;AAAA,UACR,QAAQ,yBAAyB,UAAU,OAAO;AAAA,QACpD,CAAC;AAAA,MACH,WAAW,UAAU,iBAAiB;AACpC,YAAI;AACF,gBAAM,QAAQ,IAAI,OAAO,UAAU,iBAAiB,GAAG;AACvD,gBAAMA,UAAS,MAAM,KAAK,MAAM;AAChC,kBAAQ,KAAK;AAAA,YACX,WAAW,UAAU;AAAA,YACrB,QAAAA;AAAA,YACA,QAAQA,UACJ,oCACA,2CAA2C,UAAU,eAAe;AAAA,UAC1E,CAAC;AAAA,QACH,QAAQ;AAEN,gBAAMA,UAAS,OAAO,SAAS,UAAU,eAAe;AACxD,kBAAQ,KAAK;AAAA,YACX,WAAW,UAAU;AAAA,YACrB,QAAAA;AAAA,YACA,QAAQA,UAAS,kCAAkC;AAAA,UACrD,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AAEL,gBAAQ,KAAK;AAAA,UACX,WAAW,UAAU;AAAA,UACrB,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF,OAAO;AAEL,cAAQ,KAAK;AAAA,QACX,WAAW,UAAU;AAAA,QACrB,QAAQ;AAAA,QACR,QAAQ,UAAU,UAAU,yBAAyB,UAAU,OAAO,KAAK;AAAA,MAC7E,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,WAAW;AAE1E,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,OAAO,KAAK;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAKO,SAAS,cACd,YACA,YACA,eACA,gBACmB;AACnB,QAAM,QAAQ,oBAAoB,YAAY,UAAU;AACxD,SAAO,MAAM,IAAI,CAAC,SAAS,UAAU,MAAM,eAAe,cAAc,CAAC;AAC3E;","names":["passed"]}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import {
|
|
4
4
|
log
|
|
5
5
|
} from "./chunk-BSKDOFRT.js";
|
|
6
|
-
import "./chunk-ZZJOFKAT.js";
|
|
7
6
|
|
|
8
7
|
// src/runtime/versioning.ts
|
|
9
8
|
import { existsSync, writeFileSync } from "fs";
|
|
@@ -268,4 +267,4 @@ export {
|
|
|
268
267
|
snapshot,
|
|
269
268
|
tagVersion
|
|
270
269
|
};
|
|
271
|
-
//# sourceMappingURL=versioning-
|
|
270
|
+
//# sourceMappingURL=versioning-WEGF6KJG.js.map
|