chainlesschain 0.166.14 → 0.166.16
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-C8SyFhOo.js → AIOps-CTxa2Wbq.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Dup5gFK0.js → ActionButton-DHrQyYhy.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-8FyOWA7V.js → Analytics-DQiUqcNl.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DBzdU9dg.js → AppLayout-_Iz4r6wt.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C_0Z8gNy.js → Artifacts-BNs63YHo.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Cf2a-58m.js → Audit-BgHIqRLn.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-M7w-2zgB.js → BackgroundAgents-DUQ5axBB.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CCTHQXoL.js → Backup-CJ51E9Vw.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DjXpeFwJ.js → BaseInput-o-5toA_R.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-cAvchsJU.js → Chat-Cu7a-x1L.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DvEvxlZv.js → ChatBubbleRenderer-BUyYkAXr.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DKs6qvOn.js → Checkbox-DwdmZ71L.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DlkA7b9X.js → Codegen-BrH2eYiP.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CkfdKhyJ.js → Col-BVJQEP2j.js} +1 -1
- package/src/assets/web-panel/assets/{Community-iNXMHzj6.js → Community-FGO3vZPq.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DQxPEd09.js → Compact-DYGAFVnT.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CSKvMCjs.js → Compliance-CRvc8Bdt.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CkWqazpD.js → Cowork-D6Ah2m7A.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-k6W8IzhB.js → Cron-AXbU2ifR.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-C8MS1OuY.js → Crosschain-C5DXgEFd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DJuCcgiQ.js → DID-Bxh9QV9v.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-LeThKzp3.js → Dashboard-B-ZHEta-.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BvxwgpE4.js → Dropdown-66Ik1jqa.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-n3e_a1M3.js → EmailListRenderer-CCWk1BIM.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-509W6W6v.js → FamilyGuardDashboard--9faIULK.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-xgZReQm0.js → Federation-CejW9CgX.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-jZOhKcQx.js → FormItemContext-acHgcXki.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Jo0ULzHv.js → GenericCardRenderer-D2ghd_K4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BsRcyWQP.js → Git-BOe1YJrV.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-JlpMYTAk.js → Governance-BjgzJsof.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CUEz872-.js → Inference-CXCD5rnP.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DgrvvEDU.js → KnowledgeGraph-CHA8QgJT.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Df-KY4U3.js → Logs-DORPMcL6.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CPPHz01z.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-kPZDTOeI.js → Marketplace-BCFlo1Yx.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CRRzPqNX.js → McpTools-Dl_uZAKh.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-D6pNSkg9.js → Memory-DupHamwz.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-g-pvtwRC.js → MobileBridge-BRSMPJxW.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Bzayfrfe.js → MobileProjects-BHpuzsDl.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-B7S3W-XL.js → Mtc-Ch0jOQfZ.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-B8OMEWVb.js → MtcAudit-qzwQrDi7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-CowRp7GH.js → Multisig-B5wHzUcm.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BkQNyi4c.js → NLProgramming-Hy9uAiA0.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cm7gtkKz.js → Notes-DkuchMC7.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-CbPTVh9m.js → NotificationSettings-CYZhD7Pi.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Dswi26mJ.js → OrderTableRenderer-DKOoqajt.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BkjQ9yy7.js → Organization-D9AXn6j8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DmRFXP7Y.js → Overflow-Bg15J6hX.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DQE1toAu.js → P2P--B54HoQn.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-Cz9Kk2Nb.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-xSjfOaiU.js → Permissions-DHx38eS1.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B9ICLqVP.js → PersonalDataHub-Dw8i2hAs.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CUNwyN9E.js → Pipeline-B2o6Ur2G.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-SOoCricX.js → Privacy-8g-h2fnu.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CSaFul0F.js → ProjectInit-DuVTcINx.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CueiB_oe.js → ProjectSettings-C8njvdZX.js} +2 -2
- package/src/assets/web-panel/assets/Projects-Ca9BAfu7.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CTqf_31h.js → Providers-wFqgod_W.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C8ZP31E9.js → QrScannerModal-9NCXrpmA.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CPgn4713.js → QuickAsk-WUTNvFkY.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-8_e0zw0i.js → Recommend-xT4nYmju.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-iT7lbGqw.js → RemoteSession-fak1PBGC.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-B4q9SUOz.js → Reputation-D1AkQvD-.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CA_nASyL.js → Row-DVfm6xGt.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Cwb0E3yv.js → RssFeed-BDunoLSE.js} +3 -3
- package/src/assets/web-panel/assets/{Search-D9iVsOxJ.js → Search-OPycRoU3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DPLbYchG.js → Security-C8-Fm3Dt.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DGMVL_LF.js → Services-qM_sMpMl.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-B_6kym0D.js → Skeleton-D3HBJjVF.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Ds6r0dXB.js → Skills-jr67X-tP.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BLHAbBhb.js → Sla-CS3ggyCg.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CZuddd14.js → SpeechSettings-CTOCYxOi.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CdBq7oSl.js → SyncSettings-D3s3cLT5.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DfT60Nyl.js → Tasks-CdfoJ25m.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-RLfsKy2V.js → Templates-Drb4AI8X.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Cw8xCNLE.js → Tenant-DzwR3Qkz.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BOUkNu4f.js → Terminal-D-ESq7xp.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CgGZWaRm.js → TimelineRenderer-BK-AUuws.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CpwjVyir.js → Tokens-xGTouuNp.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DL21H-eb.js → Trigger-BvZj-v2H.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B3ffFclW.js → Trust-BLC9CGmc.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CkBeb5aI.js → UkeySign-BSuCXpmE.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-U2iVaE3r.js → VideoEditing-DT3A-_Ql.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CQk2wzYd.js → Wallet-c1OXao85.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DYIuIYzT.js → WebAuthn-CuA-F7KJ.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-tg1c2mZQ.js → WorkflowEditor-MANZrf82.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CtL61spq.js → chat-D4Y4Ey0O.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DKpbeo8s.js → colors-DDH06hKO.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-gPZsrswt.js → compact-item-Cym756Xj.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CfHlwzVr.js → createContext-By4Nb1k-.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-KO3pAGeO.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-MV47g_6X.js → hasIn-Ch7OY258.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMMvlLp9.js → index-6-2eYBXr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsH-i_3d.js → index-6CpOV3Pn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CB1HnbNK.js → index-ArMrnx0v.js} +1 -1
- package/src/assets/web-panel/assets/{index-DU4rZpiK.js → index-B51otVoZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRv_uMLV.js → index-BBBUn5v7.js} +1 -1
- package/src/assets/web-panel/assets/{index-srBc9U_s.js → index-BFydNU6a.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnZ_zS1M.js → index-BIrRX-ra.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGtEBodr.js → index-BZB9q-Lp.js} +1 -1
- package/src/assets/web-panel/assets/index-Bh4Rgkg2.js +1 -0
- package/src/assets/web-panel/assets/{index-BjlPD7GO.js → index-Bo20BfdS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_B9LiKs.js → index-CF3Lhyns.js} +1 -1
- package/src/assets/web-panel/assets/{index-Rb7CRMw9.js → index-CGsvEbbq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIeJmlAA.js → index-CJPks33Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-xUlgG0Xe.js → index-CT-3FYwd.js} +1 -1
- package/src/assets/web-panel/assets/{index-6G0ETFTW.js → index-CUSUzaB8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzfxTSPW.js → index-CV1UC4zo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmPJpLoD.js → index-CV8qXTvb.js} +1 -1
- package/src/assets/web-panel/assets/{index-fYcS7NS0.js → index-CZn6dmZe.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cjv0kKCU.js → index-CjNUTBwe.js} +1 -1
- package/src/assets/web-panel/assets/{index-Df9sWKpl.js → index-CpudmDhf.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9RF4ieu.js → index-CykROEJA.js} +1 -1
- package/src/assets/web-panel/assets/{index-Zvkt9xqi.js → index-D5sADPGY.js} +1 -1
- package/src/assets/web-panel/assets/{index-yxxXlqBA.js → index-D9le0-dc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBw52sEl.js → index-DAkk1M7x.js} +1 -1
- package/src/assets/web-panel/assets/{index-ExAGPAnX.js → index-DNEJzc0r.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQp5M0s1.js → index-DNlxihs8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ddcpj1El.js → index-DiewptTL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOyGUuBE.js → index-DjwgJ5Jd.js} +1 -1
- package/src/assets/web-panel/assets/index-Dox_coTY.js +1 -0
- package/src/assets/web-panel/assets/{index-CmM9jqwe.js → index-DtGValNi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfLyHCWi.js → index-DvnywwUj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN-wxSQ8.js → index-OU8DZ9BL.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1XB5-t5.js → index-XGeQMvWK.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4lF-f_z.js → index-ZY2Qs_Sn.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9gtY4nG.js → index-jx7kr6mB.js} +3 -3
- package/src/assets/web-panel/assets/{index-C6nC_DyY.js → index-mL3nOcH6.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dd19vRwy.js → index-oBsfUbyH.js} +1 -1
- package/src/assets/web-panel/assets/{index-bSFrAszw.js → index-q-s04OKn.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_Pl1GnW.js → index-tZ5BUdad.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BeOZoSZ4.js → initDefaultProps-DGcX9iPP.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BXE8VMKx.js → motion-2SeL1D8A.js} +1 -1
- package/src/assets/web-panel/assets/{move-I9sdvPGX.js → move-3cYbQCpS.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-d25zrJd0.js → mtc-parser-IDdxfw0z.js} +1 -1
- package/src/assets/web-panel/assets/{omit-D71V_T4e.js → omit-BNswdKO6.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BNZKpIAR.js → pickAttrs-BwCnem2I.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-jOcgxTTz.js → placementArrow-B9PtZ5rq.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-pta_O8AU.js → responsiveObserve-CPB6iSzV.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Dlq6om7z.js → slide-DZgyblZa.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BnkO_yRv.js → statusUtils-D82IBdsx.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-p8NfWmcG.js → styleChecker-qSHtosM-.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-KcjZZ5U7.js → useFlexGapSupport-CmWO7h0I.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-gqonDMWB.js → useFs-yLrs_ub4.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-jIIfE4go.js → usePersonalDataHub-CJ1VW6rh.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Dyo7fBrM.js → vnode-CzY_moQ4.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-6l8BMg9W.js → zoom-DAa7rEY5.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +62 -11
- package/src/commands/learning.js +71 -1
- package/src/commands/team.js +100 -3
- package/src/data/changelog.json +29 -3
- package/src/harness/worktree-isolator.js +74 -9
- package/src/lib/agent-team/task-lease.js +12 -1
- package/src/lib/agent-team/team-graph-runtime-adapter.js +6 -0
- package/src/lib/agent-team/team-runner.js +4 -0
- package/src/lib/agent-team/team-worktree.js +4 -0
- package/src/lib/capability-manifest.js +212 -1
- package/src/lib/evolution/evolution-artifact-ports.js +3277 -0
- package/src/lib/evolution/evolution-eval-gate.js +6442 -0
- package/src/lib/evolution/evolution-evidence-projector.js +3786 -0
- package/src/lib/evolution/evolution-ledger-ports.js +2689 -0
- package/src/lib/evolution/evolution-ledger.js +4280 -0
- package/src/lib/evolution/skill-candidate-registry.js +2258 -0
- package/src/lib/evolution/skill-execution-manifest.js +942 -0
- package/src/lib/evolution/skill-mutation-authority.js +1677 -0
- package/src/lib/evolution/skill-promotion-controller.js +506 -0
- package/src/lib/evolution/skill-release-registry.js +3277 -0
- package/src/lib/evolution/skill-target-matrix-eval.js +3233 -0
- package/src/lib/evolution/skill-writer-inventory-manifest.js +998 -0
- package/src/lib/evolution/skill-writer-inventory-validator.js +816 -0
- package/src/lib/formal-quality-eval-runtime.js +192 -0
- package/src/lib/graph-kernel/cutover-contract.js +7 -0
- package/src/lib/graph-kernel/cutover-ledger.js +8 -7
- package/src/lib/graph-kernel/evidence-digest.js +47 -0
- package/src/lib/graph-kernel/graph-runtime-surfaces.json +8 -1
- package/src/lib/graph-kernel/production-cutover-evidence.js +342 -15
- package/src/lib/graph-kernel/production-source-evidence.js +1599 -0
- package/src/lib/graph-kernel/retirement-evidence.js +15 -5
- package/src/lib/graph-kernel/runtime-surface-manifest.js +67 -2
- package/src/lib/headless-manifest.js +3 -1
- package/src/lib/learning/skill-improver.js +311 -43
- package/src/lib/learning/skill-synthesizer.js +236 -16
- package/src/lib/process-execution-broker/index.js +32 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +5 -4
- package/src/lib/secure-fs.js +45 -2
- package/src/runtime/agent-core.js +17 -5
- package/src/runtime/headless-runner.js +16 -9
- package/src/assets/web-panel/assets/MarkdownRenderer-BEPDcSwz.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-BujyoDRG.js +0 -7
- package/src/assets/web-panel/assets/Projects-BOeXvU7y.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DdqnMhzi.js +0 -1
- package/src/assets/web-panel/assets/index-BcK8o4ke.js +0 -1
- package/src/assets/web-panel/assets/index-DvbAjyYI.js +0 -1
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
SKILL_WRITER_INVENTORY,
|
|
8
|
+
SKILL_WRITER_INVENTORY_SCHEMA,
|
|
9
|
+
SKILL_WRITER_SURFACES,
|
|
10
|
+
SKILL_WRITER_TARGET_AUTHORITIES,
|
|
11
|
+
SKILL_WRITER_TRIGGER_CLASSES,
|
|
12
|
+
} from "./skill-writer-inventory-manifest.js";
|
|
13
|
+
|
|
14
|
+
const INVENTORY_DIGEST_DOMAIN =
|
|
15
|
+
"chainlesschain.skill-writer-inventory.digest/v1\0";
|
|
16
|
+
const SOURCE_EXTENSIONS = new Set(
|
|
17
|
+
SKILL_WRITER_INVENTORY.scannerScope.sourceExtensions,
|
|
18
|
+
);
|
|
19
|
+
const EXCLUDED_DIRECTORIES = new Set([
|
|
20
|
+
"node_modules",
|
|
21
|
+
"dist",
|
|
22
|
+
"coverage",
|
|
23
|
+
"__tests__",
|
|
24
|
+
"test",
|
|
25
|
+
"tests",
|
|
26
|
+
"data",
|
|
27
|
+
"templates",
|
|
28
|
+
]);
|
|
29
|
+
const SKILL_MD_REFERENCE = /\bSKILL\.md\b/u;
|
|
30
|
+
const JAVASCRIPT_MUTATION_CALL =
|
|
31
|
+
/\b(?:[A-Za-z_$][\w$]*\.)*(writeFileSync|writeFile|appendFileSync|appendFile|copyFileSync|copyFile|renameSync|rename|rmSync|rm|unlinkSync|unlink|writeSafePackageComponent|hotLoadSkill)\s*\(/u;
|
|
32
|
+
const KOTLIN_MUTATION_CALL =
|
|
33
|
+
/\b(?:[A-Za-z_$][\w$]*\.)*(writeText|delete|register|unregister)\s*\(/u;
|
|
34
|
+
const SAFE_ID = /^[a-z][a-z0-9-]{2,127}$/u;
|
|
35
|
+
const SAFE_RELATIVE_FILE = /^[^\\:]+(?:\/[^\\:]*)*$/u;
|
|
36
|
+
const NON_METHOD_KEYWORDS = new Set([
|
|
37
|
+
"catch",
|
|
38
|
+
"for",
|
|
39
|
+
"if",
|
|
40
|
+
"switch",
|
|
41
|
+
"while",
|
|
42
|
+
"with",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const DEFAULT_REPOSITORY_ROOT = path.resolve(
|
|
46
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
47
|
+
"../../../../..",
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
export class SkillWriterInventoryError extends Error {
|
|
51
|
+
constructor(message, report) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.name = "SkillWriterInventoryError";
|
|
54
|
+
this.code = "SKILL_WRITER_INVENTORY_INVALID";
|
|
55
|
+
this.report = report;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function compareStrings(left, right) {
|
|
60
|
+
return String(left).localeCompare(String(right), "en", {
|
|
61
|
+
sensitivity: "case",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function canonicalValue(value) {
|
|
66
|
+
if (Array.isArray(value)) return value.map(canonicalValue);
|
|
67
|
+
if (!value || typeof value !== "object") return value;
|
|
68
|
+
return Object.fromEntries(
|
|
69
|
+
Object.keys(value)
|
|
70
|
+
.sort(compareStrings)
|
|
71
|
+
.map((key) => [key, canonicalValue(value[key])]),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizedEvidence(value) {
|
|
76
|
+
return [...new Set(Array.isArray(value) ? value.map(String) : [])].sort(
|
|
77
|
+
compareStrings,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Return the order-independent canonical inventory projection. */
|
|
82
|
+
export function canonicalSkillWriterInventory(
|
|
83
|
+
inventory = SKILL_WRITER_INVENTORY,
|
|
84
|
+
) {
|
|
85
|
+
const writers = Array.isArray(inventory?.writers)
|
|
86
|
+
? inventory.writers.map((record) => ({
|
|
87
|
+
id: String(record?.id || ""),
|
|
88
|
+
surface: String(record?.surface || ""),
|
|
89
|
+
triggerClass: String(record?.triggerClass || ""),
|
|
90
|
+
targetAuthority: String(record?.targetAuthority || ""),
|
|
91
|
+
mutationType: String(record?.mutationType || ""),
|
|
92
|
+
discoverySymbol:
|
|
93
|
+
record?.discoverySymbol == null
|
|
94
|
+
? null
|
|
95
|
+
: String(record.discoverySymbol),
|
|
96
|
+
entrypoint: {
|
|
97
|
+
file: String(record?.entrypoint?.file || ""),
|
|
98
|
+
symbol: String(record?.entrypoint?.symbol || ""),
|
|
99
|
+
evidence: normalizedEvidence(record?.entrypoint?.evidence),
|
|
100
|
+
},
|
|
101
|
+
mutation: {
|
|
102
|
+
file: String(record?.mutation?.file || ""),
|
|
103
|
+
symbol: String(record?.mutation?.symbol || ""),
|
|
104
|
+
evidence: normalizedEvidence(record?.mutation?.evidence),
|
|
105
|
+
},
|
|
106
|
+
}))
|
|
107
|
+
: [];
|
|
108
|
+
writers.sort((left, right) => compareStrings(left.id, right.id));
|
|
109
|
+
const scopeExclusions = Array.isArray(inventory?.scopeExclusions)
|
|
110
|
+
? inventory.scopeExclusions.map((record) => ({
|
|
111
|
+
id: String(record?.id || ""),
|
|
112
|
+
surface: String(record?.surface || ""),
|
|
113
|
+
reasonCode: String(record?.reasonCode || ""),
|
|
114
|
+
evidence: {
|
|
115
|
+
file: String(record?.evidence?.file || ""),
|
|
116
|
+
symbol: String(record?.evidence?.symbol || ""),
|
|
117
|
+
evidence: normalizedEvidence(record?.evidence?.evidence),
|
|
118
|
+
},
|
|
119
|
+
}))
|
|
120
|
+
: [];
|
|
121
|
+
scopeExclusions.sort((left, right) => compareStrings(left.id, right.id));
|
|
122
|
+
const scannerScope = inventory?.scannerScope || {};
|
|
123
|
+
return canonicalValue({
|
|
124
|
+
schema: String(inventory?.schema || ""),
|
|
125
|
+
schemaVersion: Number(inventory?.schemaVersion),
|
|
126
|
+
surfaces: [...new Set((inventory?.surfaces || []).map(String))].sort(
|
|
127
|
+
compareStrings,
|
|
128
|
+
),
|
|
129
|
+
sourceRoots: [...new Set((inventory?.sourceRoots || []).map(String))].sort(
|
|
130
|
+
compareStrings,
|
|
131
|
+
),
|
|
132
|
+
scannerScope: {
|
|
133
|
+
classification: String(scannerScope.classification || ""),
|
|
134
|
+
unit: String(scannerScope.unit || ""),
|
|
135
|
+
sourceExtensions: [
|
|
136
|
+
...new Set((scannerScope.sourceExtensions || []).map(String)),
|
|
137
|
+
].sort(compareStrings),
|
|
138
|
+
skillBindings: [
|
|
139
|
+
...new Set((scannerScope.skillBindings || []).map(String)),
|
|
140
|
+
].sort(compareStrings),
|
|
141
|
+
mutationSinks: [
|
|
142
|
+
...new Set((scannerScope.mutationSinks || []).map(String)),
|
|
143
|
+
].sort(compareStrings),
|
|
144
|
+
},
|
|
145
|
+
limitations: [...new Set((inventory?.limitations || []).map(String))].sort(
|
|
146
|
+
compareStrings,
|
|
147
|
+
),
|
|
148
|
+
scopeExclusions,
|
|
149
|
+
writers,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function skillWriterInventoryDigest(inventory = SKILL_WRITER_INVENTORY) {
|
|
154
|
+
const canonical = JSON.stringify(canonicalSkillWriterInventory(inventory));
|
|
155
|
+
return `sha256:${crypto
|
|
156
|
+
.createHash("sha256")
|
|
157
|
+
.update(INVENTORY_DIGEST_DOMAIN)
|
|
158
|
+
.update(canonical)
|
|
159
|
+
.digest("hex")}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function posixRelative(value) {
|
|
163
|
+
return String(value).replaceAll("\\", "/");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isContained(root, candidate) {
|
|
167
|
+
const relative = path.relative(root, candidate);
|
|
168
|
+
return (
|
|
169
|
+
relative !== "" &&
|
|
170
|
+
relative !== ".." &&
|
|
171
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
172
|
+
!path.isAbsolute(relative)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function listSourceFiles(repositoryRoot, sourceRoots) {
|
|
177
|
+
const result = [];
|
|
178
|
+
const visit = (directory) => {
|
|
179
|
+
const entries = fs
|
|
180
|
+
.readdirSync(directory, { withFileTypes: true })
|
|
181
|
+
.sort((left, right) => compareStrings(left.name, right.name));
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const target = path.join(directory, entry.name);
|
|
184
|
+
if (entry.isDirectory()) {
|
|
185
|
+
if (!EXCLUDED_DIRECTORIES.has(entry.name)) visit(target);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (
|
|
189
|
+
entry.isFile() &&
|
|
190
|
+
SOURCE_EXTENSIONS.has(path.extname(entry.name).toLowerCase())
|
|
191
|
+
) {
|
|
192
|
+
result.push(target);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
for (const relativeRoot of sourceRoots) {
|
|
197
|
+
const absoluteRoot = path.resolve(repositoryRoot, relativeRoot);
|
|
198
|
+
if (fs.existsSync(absoluteRoot)) visit(absoluteRoot);
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Remove comments and string/template contents while preserving line numbers.
|
|
205
|
+
* Template bodies are intentionally removed wholesale: source text embedded in
|
|
206
|
+
* a generated handler is not a mutation executed by the containing module.
|
|
207
|
+
*/
|
|
208
|
+
function stripNonCode(source) {
|
|
209
|
+
let state = "code";
|
|
210
|
+
let escaped = false;
|
|
211
|
+
let output = "";
|
|
212
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
213
|
+
const char = source[index];
|
|
214
|
+
const next = source[index + 1];
|
|
215
|
+
if (state === "line-comment") {
|
|
216
|
+
if (char === "\n") {
|
|
217
|
+
state = "code";
|
|
218
|
+
output += "\n";
|
|
219
|
+
} else output += " ";
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (state === "block-comment") {
|
|
223
|
+
if (char === "*" && next === "/") {
|
|
224
|
+
output += " ";
|
|
225
|
+
index += 1;
|
|
226
|
+
state = "code";
|
|
227
|
+
} else output += char === "\n" ? "\n" : " ";
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (state !== "code") {
|
|
231
|
+
if (char === "\n") output += "\n";
|
|
232
|
+
else output += " ";
|
|
233
|
+
if (escaped) {
|
|
234
|
+
escaped = false;
|
|
235
|
+
} else if (char === "\\") {
|
|
236
|
+
escaped = true;
|
|
237
|
+
} else if (
|
|
238
|
+
(state === "single" && char === "'") ||
|
|
239
|
+
(state === "double" && char === '"') ||
|
|
240
|
+
(state === "template" && char === "`")
|
|
241
|
+
) {
|
|
242
|
+
state = "code";
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (char === "/" && next === "/") {
|
|
247
|
+
output += " ";
|
|
248
|
+
index += 1;
|
|
249
|
+
state = "line-comment";
|
|
250
|
+
} else if (char === "/" && next === "*") {
|
|
251
|
+
output += " ";
|
|
252
|
+
index += 1;
|
|
253
|
+
state = "block-comment";
|
|
254
|
+
} else if (char === "'") {
|
|
255
|
+
output += " ";
|
|
256
|
+
state = "single";
|
|
257
|
+
} else if (char === '"') {
|
|
258
|
+
output += " ";
|
|
259
|
+
state = "double";
|
|
260
|
+
} else if (char === "`") {
|
|
261
|
+
output += " ";
|
|
262
|
+
state = "template";
|
|
263
|
+
} else {
|
|
264
|
+
output += char;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return output;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function nearestWriterSymbol(rawLines, codeLines, mutationLine) {
|
|
271
|
+
let best = null;
|
|
272
|
+
const consider = (line, symbol) => {
|
|
273
|
+
if (!symbol || (best && best.line > line)) return;
|
|
274
|
+
best = { line, symbol };
|
|
275
|
+
};
|
|
276
|
+
const lowerBound = Math.max(0, mutationLine - 500);
|
|
277
|
+
for (let line = lowerBound; line <= mutationLine; line += 1) {
|
|
278
|
+
const code = codeLines[line] || "";
|
|
279
|
+
const declaration = code.match(
|
|
280
|
+
/^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/u,
|
|
281
|
+
);
|
|
282
|
+
if (declaration) consider(line, declaration[1]);
|
|
283
|
+
const kotlinFunction = code.match(
|
|
284
|
+
/^\s*(?:(?:public|private|protected|internal|override|open|final|tailrec|operator|inline|infix|external|suspend)\s+)*fun\s+([A-Za-z_$][\w$]*)\s*\(/u,
|
|
285
|
+
);
|
|
286
|
+
if (kotlinFunction) consider(line, kotlinFunction[1]);
|
|
287
|
+
const method = code.match(
|
|
288
|
+
/^\s{2}(?:async\s+)?([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*\{/u,
|
|
289
|
+
);
|
|
290
|
+
if (method && !NON_METHOD_KEYWORDS.has(method[1])) {
|
|
291
|
+
consider(line, method[1]);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const ipcWindow = rawLines.slice(line, line + 4).join("\n");
|
|
295
|
+
const ipc = ipcWindow.match(/ipcMain\.handle\(\s*["']([^"']+)["']/u);
|
|
296
|
+
if (ipc) consider(line, `ipc:${ipc[1]}`);
|
|
297
|
+
}
|
|
298
|
+
return best?.symbol || "<module>";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function contextBindsSkillMd(rawLines, mutationLine, completeSource) {
|
|
302
|
+
const start = Math.max(0, mutationLine - 50);
|
|
303
|
+
const end = Math.min(rawLines.length, mutationLine + 51);
|
|
304
|
+
const context = rawLines.slice(start, end).join("\n");
|
|
305
|
+
if (SKILL_MD_REFERENCE.test(context)) return true;
|
|
306
|
+
return (
|
|
307
|
+
/\bPACKAGE_FILES\b/u.test(context) &&
|
|
308
|
+
/\bPACKAGE_FILES\b[\s\S]{0,500}["'`]SKILL\.md["'`]/u.test(completeSource)
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Discover direct production writers independently of the inventory. The
|
|
314
|
+
* scanner works at writer-function/IPC granularity, not call-site line number,
|
|
315
|
+
* so harmless formatting changes do not churn inventory identity.
|
|
316
|
+
*/
|
|
317
|
+
export function discoverSkillWriterSites({
|
|
318
|
+
repositoryRoot = DEFAULT_REPOSITORY_ROOT,
|
|
319
|
+
sourceRoots = SKILL_WRITER_INVENTORY.sourceRoots,
|
|
320
|
+
} = {}) {
|
|
321
|
+
const resolvedRoot = path.resolve(repositoryRoot);
|
|
322
|
+
const discovered = new Map();
|
|
323
|
+
for (const filePath of listSourceFiles(resolvedRoot, sourceRoots)) {
|
|
324
|
+
const source = fs.readFileSync(filePath, "utf8");
|
|
325
|
+
const rawLines = source.split(/\r?\n/u);
|
|
326
|
+
const codeLines = stripNonCode(source).split(/\r?\n/u);
|
|
327
|
+
const mutationCall =
|
|
328
|
+
path.extname(filePath).toLowerCase() === ".kt"
|
|
329
|
+
? KOTLIN_MUTATION_CALL
|
|
330
|
+
: JAVASCRIPT_MUTATION_CALL;
|
|
331
|
+
for (let line = 0; line < codeLines.length; line += 1) {
|
|
332
|
+
const mutation = codeLines[line].match(mutationCall);
|
|
333
|
+
if (!mutation || !contextBindsSkillMd(rawLines, line, source)) continue;
|
|
334
|
+
const symbol = nearestWriterSymbol(rawLines, codeLines, line);
|
|
335
|
+
const file = posixRelative(path.relative(resolvedRoot, filePath));
|
|
336
|
+
const key = `${file}::${symbol}`;
|
|
337
|
+
const existing = discovered.get(key) || {
|
|
338
|
+
key,
|
|
339
|
+
file,
|
|
340
|
+
symbol,
|
|
341
|
+
operations: new Set(),
|
|
342
|
+
};
|
|
343
|
+
existing.operations.add(mutation[1]);
|
|
344
|
+
discovered.set(key, existing);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return [...discovered.values()]
|
|
348
|
+
.map((site) => ({
|
|
349
|
+
key: site.key,
|
|
350
|
+
file: site.file,
|
|
351
|
+
symbol: site.symbol,
|
|
352
|
+
operations: [...site.operations].sort(compareStrings),
|
|
353
|
+
}))
|
|
354
|
+
.sort((left, right) => compareStrings(left.key, right.key));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function discoveryKeys(inventory) {
|
|
358
|
+
return new Set(
|
|
359
|
+
(inventory?.writers || [])
|
|
360
|
+
.filter((writer) => typeof writer?.discoverySymbol === "string")
|
|
361
|
+
.map(
|
|
362
|
+
(writer) =>
|
|
363
|
+
`${posixRelative(writer.mutation.file)}::${writer.discoverySymbol}`,
|
|
364
|
+
),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function findUnclassifiedSkillWriters(
|
|
369
|
+
discovered,
|
|
370
|
+
inventory = SKILL_WRITER_INVENTORY,
|
|
371
|
+
) {
|
|
372
|
+
const classified = discoveryKeys(inventory);
|
|
373
|
+
return (discovered || []).filter((site) => !classified.has(site.key));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function pushError(errors, code, message, details = {}) {
|
|
377
|
+
errors.push(Object.freeze({ code, message, ...details }));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function validateRecordShape(record, index, errors) {
|
|
381
|
+
const label = `writers[${index}]`;
|
|
382
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
383
|
+
pushError(errors, "WRITER_INVALID", `${label} must be an object`);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (!SAFE_ID.test(record.id || "")) {
|
|
387
|
+
pushError(errors, "WRITER_ID_INVALID", `${label}.id is invalid`, {
|
|
388
|
+
writerId: record.id || null,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
if (!SKILL_WRITER_SURFACES.includes(record.surface)) {
|
|
392
|
+
pushError(errors, "WRITER_SURFACE_INVALID", `${label}.surface is invalid`, {
|
|
393
|
+
writerId: record.id || null,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
if (!SKILL_WRITER_TRIGGER_CLASSES.includes(record.triggerClass)) {
|
|
397
|
+
pushError(
|
|
398
|
+
errors,
|
|
399
|
+
"WRITER_TRIGGER_INVALID",
|
|
400
|
+
`${label}.triggerClass is invalid`,
|
|
401
|
+
{
|
|
402
|
+
writerId: record.id || null,
|
|
403
|
+
},
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (!SKILL_WRITER_TARGET_AUTHORITIES.includes(record.targetAuthority)) {
|
|
407
|
+
pushError(
|
|
408
|
+
errors,
|
|
409
|
+
"WRITER_AUTHORITY_INVALID",
|
|
410
|
+
`${label}.targetAuthority is invalid`,
|
|
411
|
+
{ writerId: record.id || null },
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (typeof record.mutationType !== "string" || !record.mutationType) {
|
|
415
|
+
pushError(
|
|
416
|
+
errors,
|
|
417
|
+
"WRITER_MUTATION_TYPE_INVALID",
|
|
418
|
+
`${label}.mutationType is invalid`,
|
|
419
|
+
{
|
|
420
|
+
writerId: record.id || null,
|
|
421
|
+
},
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
for (const field of ["entrypoint", "mutation"]) {
|
|
425
|
+
const evidence = record[field];
|
|
426
|
+
if (!evidence || typeof evidence !== "object") {
|
|
427
|
+
pushError(
|
|
428
|
+
errors,
|
|
429
|
+
"WRITER_EVIDENCE_INVALID",
|
|
430
|
+
`${label}.${field} is invalid`,
|
|
431
|
+
{
|
|
432
|
+
writerId: record.id || null,
|
|
433
|
+
},
|
|
434
|
+
);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
if (
|
|
438
|
+
!SAFE_RELATIVE_FILE.test(evidence.file || "") ||
|
|
439
|
+
evidence.file.includes("\0") ||
|
|
440
|
+
path.posix.isAbsolute(evidence.file || "") ||
|
|
441
|
+
posixRelative(evidence.file).split("/").includes("..")
|
|
442
|
+
) {
|
|
443
|
+
pushError(
|
|
444
|
+
errors,
|
|
445
|
+
"WRITER_FILE_INVALID",
|
|
446
|
+
`${label}.${field}.file is invalid`,
|
|
447
|
+
{
|
|
448
|
+
writerId: record.id || null,
|
|
449
|
+
file: evidence.file || null,
|
|
450
|
+
},
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
if (typeof evidence.symbol !== "string" || !evidence.symbol) {
|
|
454
|
+
pushError(
|
|
455
|
+
errors,
|
|
456
|
+
"WRITER_SYMBOL_INVALID",
|
|
457
|
+
`${label}.${field}.symbol is invalid`,
|
|
458
|
+
{
|
|
459
|
+
writerId: record.id || null,
|
|
460
|
+
},
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
if (
|
|
464
|
+
!Array.isArray(evidence.evidence) ||
|
|
465
|
+
evidence.evidence.length === 0 ||
|
|
466
|
+
evidence.evidence.some((value) => typeof value !== "string" || !value)
|
|
467
|
+
) {
|
|
468
|
+
pushError(
|
|
469
|
+
errors,
|
|
470
|
+
"WRITER_EVIDENCE_INVALID",
|
|
471
|
+
`${label}.${field}.evidence must contain non-empty strings`,
|
|
472
|
+
{ writerId: record.id || null },
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function validateScopeExclusionShape(record, index, errors) {
|
|
479
|
+
const label = `scopeExclusions[${index}]`;
|
|
480
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
481
|
+
pushError(errors, "SCOPE_EXCLUSION_INVALID", `${label} must be an object`);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (!SAFE_ID.test(record.id || "")) {
|
|
485
|
+
pushError(errors, "SCOPE_EXCLUSION_ID_INVALID", `${label}.id is invalid`, {
|
|
486
|
+
exclusionId: record.id || null,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
if (!SKILL_WRITER_SURFACES.includes(record.surface)) {
|
|
490
|
+
pushError(
|
|
491
|
+
errors,
|
|
492
|
+
"SCOPE_EXCLUSION_SURFACE_INVALID",
|
|
493
|
+
`${label}.surface is invalid`,
|
|
494
|
+
{
|
|
495
|
+
exclusionId: record.id || null,
|
|
496
|
+
},
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (!SAFE_ID.test(record.reasonCode || "")) {
|
|
500
|
+
pushError(
|
|
501
|
+
errors,
|
|
502
|
+
"SCOPE_EXCLUSION_REASON_INVALID",
|
|
503
|
+
`${label}.reasonCode is invalid`,
|
|
504
|
+
{
|
|
505
|
+
exclusionId: record.id || null,
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
const evidence = record.evidence;
|
|
510
|
+
if (
|
|
511
|
+
!evidence ||
|
|
512
|
+
typeof evidence !== "object" ||
|
|
513
|
+
!SAFE_RELATIVE_FILE.test(evidence.file || "") ||
|
|
514
|
+
evidence.file.includes("\0") ||
|
|
515
|
+
path.posix.isAbsolute(evidence.file || "") ||
|
|
516
|
+
posixRelative(evidence.file || "")
|
|
517
|
+
.split("/")
|
|
518
|
+
.includes("..") ||
|
|
519
|
+
typeof evidence.symbol !== "string" ||
|
|
520
|
+
!evidence.symbol ||
|
|
521
|
+
!Array.isArray(evidence.evidence) ||
|
|
522
|
+
evidence.evidence.length === 0 ||
|
|
523
|
+
evidence.evidence.some((value) => typeof value !== "string" || !value)
|
|
524
|
+
) {
|
|
525
|
+
pushError(
|
|
526
|
+
errors,
|
|
527
|
+
"SCOPE_EXCLUSION_EVIDENCE_INVALID",
|
|
528
|
+
`${label}.evidence is invalid`,
|
|
529
|
+
{
|
|
530
|
+
exclusionId: record.id || null,
|
|
531
|
+
},
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function validateEvidenceFile(
|
|
537
|
+
repositoryRoot,
|
|
538
|
+
writer,
|
|
539
|
+
kind,
|
|
540
|
+
errors,
|
|
541
|
+
sourceCache,
|
|
542
|
+
) {
|
|
543
|
+
const evidence = writer[kind];
|
|
544
|
+
if (!evidence?.file) return;
|
|
545
|
+
const absolute = path.resolve(repositoryRoot, evidence.file);
|
|
546
|
+
if (!isContained(repositoryRoot, absolute)) {
|
|
547
|
+
pushError(
|
|
548
|
+
errors,
|
|
549
|
+
"WRITER_FILE_ESCAPE",
|
|
550
|
+
"writer evidence file escapes repository",
|
|
551
|
+
{
|
|
552
|
+
writerId: writer.id,
|
|
553
|
+
kind,
|
|
554
|
+
file: evidence.file,
|
|
555
|
+
},
|
|
556
|
+
);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
let stat;
|
|
560
|
+
try {
|
|
561
|
+
stat = fs.lstatSync(absolute);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
pushError(
|
|
564
|
+
errors,
|
|
565
|
+
"WRITER_FILE_MISSING",
|
|
566
|
+
"writer evidence file is missing",
|
|
567
|
+
{
|
|
568
|
+
writerId: writer.id,
|
|
569
|
+
kind,
|
|
570
|
+
file: evidence.file,
|
|
571
|
+
cause: error.code || error.message,
|
|
572
|
+
},
|
|
573
|
+
);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
577
|
+
pushError(
|
|
578
|
+
errors,
|
|
579
|
+
"WRITER_FILE_UNSAFE",
|
|
580
|
+
"writer evidence must be a regular, non-symlink file",
|
|
581
|
+
{ writerId: writer.id, kind, file: evidence.file },
|
|
582
|
+
);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
let source = sourceCache.get(absolute);
|
|
586
|
+
if (source === undefined) {
|
|
587
|
+
source = fs.readFileSync(absolute, "utf8");
|
|
588
|
+
sourceCache.set(absolute, source);
|
|
589
|
+
}
|
|
590
|
+
for (const fragment of evidence.evidence || []) {
|
|
591
|
+
if (!source.includes(fragment)) {
|
|
592
|
+
pushError(
|
|
593
|
+
errors,
|
|
594
|
+
"WRITER_EVIDENCE_MISSING",
|
|
595
|
+
"writer source evidence fragment is missing",
|
|
596
|
+
{
|
|
597
|
+
writerId: writer.id,
|
|
598
|
+
kind,
|
|
599
|
+
file: evidence.file,
|
|
600
|
+
fragment,
|
|
601
|
+
},
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** Validate schema, source evidence, canonical classification, and discovery. */
|
|
608
|
+
export function validateSkillWriterInventory({
|
|
609
|
+
repositoryRoot = DEFAULT_REPOSITORY_ROOT,
|
|
610
|
+
inventory = SKILL_WRITER_INVENTORY,
|
|
611
|
+
sourceRoots = inventory?.sourceRoots,
|
|
612
|
+
requireClassificationCoverage = true,
|
|
613
|
+
} = {}) {
|
|
614
|
+
const resolvedRoot = path.resolve(repositoryRoot);
|
|
615
|
+
const errors = [];
|
|
616
|
+
if (
|
|
617
|
+
inventory?.schema !== SKILL_WRITER_INVENTORY_SCHEMA ||
|
|
618
|
+
inventory?.schemaVersion !== 1
|
|
619
|
+
) {
|
|
620
|
+
pushError(
|
|
621
|
+
errors,
|
|
622
|
+
"INVENTORY_SCHEMA_INVALID",
|
|
623
|
+
"writer inventory schema is invalid",
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
if (!Array.isArray(inventory?.writers)) {
|
|
627
|
+
pushError(
|
|
628
|
+
errors,
|
|
629
|
+
"INVENTORY_WRITERS_INVALID",
|
|
630
|
+
"writer inventory must contain writers",
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
if (!Array.isArray(sourceRoots) || sourceRoots.length === 0) {
|
|
634
|
+
pushError(
|
|
635
|
+
errors,
|
|
636
|
+
"INVENTORY_ROOTS_INVALID",
|
|
637
|
+
"writer inventory sourceRoots are invalid",
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
if (
|
|
641
|
+
!Array.isArray(inventory?.surfaces) ||
|
|
642
|
+
inventory.surfaces.length !== SKILL_WRITER_SURFACES.length ||
|
|
643
|
+
SKILL_WRITER_SURFACES.some(
|
|
644
|
+
(surface) => !inventory.surfaces.includes(surface),
|
|
645
|
+
)
|
|
646
|
+
) {
|
|
647
|
+
pushError(
|
|
648
|
+
errors,
|
|
649
|
+
"INVENTORY_SURFACES_INVALID",
|
|
650
|
+
"writer surfaces are invalid",
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
if (
|
|
654
|
+
inventory?.scannerScope?.classification !== "direct-source-sink-subset" ||
|
|
655
|
+
inventory?.scannerScope?.unit !== "function-or-ipc" ||
|
|
656
|
+
!Array.isArray(inventory?.scannerScope?.sourceExtensions) ||
|
|
657
|
+
!Array.isArray(inventory?.scannerScope?.skillBindings) ||
|
|
658
|
+
!Array.isArray(inventory?.scannerScope?.mutationSinks) ||
|
|
659
|
+
!Array.isArray(inventory?.limitations) ||
|
|
660
|
+
inventory.limitations.length === 0
|
|
661
|
+
) {
|
|
662
|
+
pushError(
|
|
663
|
+
errors,
|
|
664
|
+
"INVENTORY_SCANNER_SCOPE_INVALID",
|
|
665
|
+
"direct scanner scope and limitations must be machine-readable",
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
if (!Array.isArray(inventory?.scopeExclusions)) {
|
|
669
|
+
pushError(
|
|
670
|
+
errors,
|
|
671
|
+
"INVENTORY_SCOPE_EXCLUSIONS_INVALID",
|
|
672
|
+
"scope exclusions must be explicit",
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const writers = Array.isArray(inventory?.writers) ? inventory.writers : [];
|
|
677
|
+
const scopeExclusions = Array.isArray(inventory?.scopeExclusions)
|
|
678
|
+
? inventory.scopeExclusions
|
|
679
|
+
: [];
|
|
680
|
+
writers.forEach((record, index) =>
|
|
681
|
+
validateRecordShape(record, index, errors),
|
|
682
|
+
);
|
|
683
|
+
scopeExclusions.forEach((record, index) =>
|
|
684
|
+
validateScopeExclusionShape(record, index, errors),
|
|
685
|
+
);
|
|
686
|
+
const ids = new Set();
|
|
687
|
+
for (const record of writers) {
|
|
688
|
+
if (ids.has(record?.id)) {
|
|
689
|
+
pushError(errors, "WRITER_ID_DUPLICATE", "writer id is duplicated", {
|
|
690
|
+
writerId: record?.id || null,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
ids.add(record?.id);
|
|
694
|
+
}
|
|
695
|
+
for (const record of scopeExclusions) {
|
|
696
|
+
if (ids.has(record?.id)) {
|
|
697
|
+
pushError(
|
|
698
|
+
errors,
|
|
699
|
+
"INVENTORY_ID_DUPLICATE",
|
|
700
|
+
"writer and scope exclusion ids must be unique",
|
|
701
|
+
{ id: record?.id || null },
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
ids.add(record?.id);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (requireClassificationCoverage) {
|
|
708
|
+
const triggerClasses = new Set(
|
|
709
|
+
writers.map((writer) => writer.triggerClass),
|
|
710
|
+
);
|
|
711
|
+
const targetAuthorities = new Set(
|
|
712
|
+
writers.map((writer) => writer.targetAuthority),
|
|
713
|
+
);
|
|
714
|
+
for (const value of SKILL_WRITER_TRIGGER_CLASSES) {
|
|
715
|
+
if (!triggerClasses.has(value)) {
|
|
716
|
+
pushError(
|
|
717
|
+
errors,
|
|
718
|
+
"INVENTORY_CLASSIFICATION_MISSING",
|
|
719
|
+
`writer inventory does not cover ${value}`,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
for (const value of SKILL_WRITER_TARGET_AUTHORITIES) {
|
|
724
|
+
if (!targetAuthorities.has(value)) {
|
|
725
|
+
pushError(
|
|
726
|
+
errors,
|
|
727
|
+
"INVENTORY_CLASSIFICATION_MISSING",
|
|
728
|
+
`writer inventory does not cover ${value}`,
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const sourceCache = new Map();
|
|
735
|
+
for (const record of writers) {
|
|
736
|
+
validateEvidenceFile(
|
|
737
|
+
resolvedRoot,
|
|
738
|
+
record,
|
|
739
|
+
"entrypoint",
|
|
740
|
+
errors,
|
|
741
|
+
sourceCache,
|
|
742
|
+
);
|
|
743
|
+
validateEvidenceFile(resolvedRoot, record, "mutation", errors, sourceCache);
|
|
744
|
+
}
|
|
745
|
+
for (const record of scopeExclusions) {
|
|
746
|
+
validateEvidenceFile(resolvedRoot, record, "evidence", errors, sourceCache);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
let discovered = [];
|
|
750
|
+
try {
|
|
751
|
+
discovered = discoverSkillWriterSites({
|
|
752
|
+
repositoryRoot: resolvedRoot,
|
|
753
|
+
sourceRoots: Array.isArray(sourceRoots) ? sourceRoots : [],
|
|
754
|
+
});
|
|
755
|
+
} catch (error) {
|
|
756
|
+
pushError(errors, "INVENTORY_DISCOVERY_FAILED", "writer discovery failed", {
|
|
757
|
+
cause: error.code || error.message,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
const unknownDirect = findUnclassifiedSkillWriters(discovered, inventory);
|
|
761
|
+
for (const site of unknownDirect) {
|
|
762
|
+
pushError(
|
|
763
|
+
errors,
|
|
764
|
+
"UNCLASSIFIED_DIRECT_SKILL_WRITER",
|
|
765
|
+
"directly discovered production Skill writer is not classified in the inventory",
|
|
766
|
+
site,
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const discoveredKeys = new Set(discovered.map((site) => site.key));
|
|
771
|
+
for (const record of writers) {
|
|
772
|
+
if (typeof record?.discoverySymbol !== "string") continue;
|
|
773
|
+
const key = `${posixRelative(record.mutation.file)}::${record.discoverySymbol}`;
|
|
774
|
+
if (!discoveredKeys.has(key)) {
|
|
775
|
+
pushError(
|
|
776
|
+
errors,
|
|
777
|
+
"INVENTORIED_WRITER_NOT_DISCOVERED",
|
|
778
|
+
"a statically discoverable inventory writer no longer matches source",
|
|
779
|
+
{ writerId: record.id, key },
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const report = Object.freeze({
|
|
785
|
+
ok: errors.length === 0,
|
|
786
|
+
schema: SKILL_WRITER_INVENTORY_SCHEMA,
|
|
787
|
+
digest: skillWriterInventoryDigest(inventory),
|
|
788
|
+
writerCount: writers.length,
|
|
789
|
+
legacyActiveCount: writers.filter(
|
|
790
|
+
(writer) => writer.targetAuthority === "legacy-active",
|
|
791
|
+
).length,
|
|
792
|
+
candidateOnlyCount: writers.filter(
|
|
793
|
+
(writer) => writer.targetAuthority === "candidate-only",
|
|
794
|
+
).length,
|
|
795
|
+
scopeExclusionCount: scopeExclusions.length,
|
|
796
|
+
directDiscoveredCount: discovered.length,
|
|
797
|
+
unknownDirectCount: unknownDirect.length,
|
|
798
|
+
scannerScope: Object.freeze(canonicalValue(inventory.scannerScope || {})),
|
|
799
|
+
limitations: Object.freeze([...(inventory.limitations || [])]),
|
|
800
|
+
directDiscovered: Object.freeze(discovered),
|
|
801
|
+
unknownDirect: Object.freeze(unknownDirect),
|
|
802
|
+
errors: Object.freeze(errors),
|
|
803
|
+
});
|
|
804
|
+
return report;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
export function assertSkillWriterInventory(options = {}) {
|
|
808
|
+
const report = validateSkillWriterInventory(options);
|
|
809
|
+
if (!report.ok) {
|
|
810
|
+
throw new SkillWriterInventoryError(
|
|
811
|
+
`Skill writer inventory validation failed with ${report.errors.length} error(s)`,
|
|
812
|
+
report,
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
return report;
|
|
816
|
+
}
|