@ngockhoale/ukit 1.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/CHANGELOG.md +179 -0
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/bin/ukit +30 -0
- package/manifests/platform.full.yaml +1194 -0
- package/package.json +71 -0
- package/scripts/bug/triage.mjs +37 -0
- package/scripts/index/build-index.mjs +35 -0
- package/scripts/index/query-index.mjs +92 -0
- package/scripts/index/refresh-index.mjs +85 -0
- package/scripts/release/verify-release.mjs +56 -0
- package/src/bug/triageBug.js +123 -0
- package/src/cli/adapters.js +148 -0
- package/src/cli/commands/diff.js +51 -0
- package/src/cli/commands/doctor.js +125 -0
- package/src/cli/commands/indexArgs.js +73 -0
- package/src/cli/commands/indexTools.js +509 -0
- package/src/cli/commands/install.js +293 -0
- package/src/cli/commands/memory.js +126 -0
- package/src/cli/commands/status.js +8 -0
- package/src/cli/commands/uninstall.js +51 -0
- package/src/cli/index.js +109 -0
- package/src/context/detectProjectContext.js +49 -0
- package/src/context/detectProviders.js +12 -0
- package/src/core/applyPlan.js +89 -0
- package/src/core/buildPlan.js +228 -0
- package/src/core/compact/index.js +294 -0
- package/src/core/compact/threshold.js +936 -0
- package/src/core/diffPlan.js +73 -0
- package/src/core/ensureGitignore.js +117 -0
- package/src/core/fileOps.js +188 -0
- package/src/core/memory/hygiene.js +160 -0
- package/src/core/memory/index.js +2 -0
- package/src/core/memory/retrieval.js +476 -0
- package/src/core/memory/store.js +202 -0
- package/src/core/metadata.js +132 -0
- package/src/core/migrateLegacy.js +139 -0
- package/src/core/output/index.js +1309 -0
- package/src/core/paths.js +13 -0
- package/src/core/report.js +17 -0
- package/src/core/router/advisor.js +42 -0
- package/src/core/router/index.js +2 -0
- package/src/core/router/router.js +164 -0
- package/src/core/runInstallPipeline.js +365 -0
- package/src/core/runtimeConfig.js +190 -0
- package/src/core/runtimePaths.js +24 -0
- package/src/core/status.js +186 -0
- package/src/core/token/index.js +328 -0
- package/src/core/uninstall.js +246 -0
- package/src/core/validation/confidence.js +89 -0
- package/src/core/validation/index.js +2 -0
- package/src/core/validation/validator.js +165 -0
- package/src/index/buildIndex.js +1392 -0
- package/src/index/gitHooks.js +109 -0
- package/src/index/importResolution.js +377 -0
- package/src/index/languageTools.js +127 -0
- package/src/index/paths.js +27 -0
- package/src/index/queryIndex.js +637 -0
- package/src/index/relatedTests.js +237 -0
- package/src/index/resolveContext.js +345 -0
- package/src/index/routeCatalog.js +258 -0
- package/src/index/taskRouting.js +677 -0
- package/src/index/verificationPlan.js +437 -0
- package/src/manifest/loadManifest.js +22 -0
- package/src/manifest/selectItems.js +78 -0
- package/src/manifest/validateManifest.js +115 -0
- package/src/render/buildVariables.js +39 -0
- package/src/render/renderTemplate.js +44 -0
- package/src/stack/detectStack.js +213 -0
- package/templates/.claude/agents/bug-debugger.md +57 -0
- package/templates/.claude/agents/feature-implementer.md +55 -0
- package/templates/.claude/config/providers.md +25 -0
- package/templates/.claude/hooks/auto-allow-bash.sh +155 -0
- package/templates/.claude/hooks/auto-prune-bash.sh +75 -0
- package/templates/.claude/hooks/block-dangerous.sh +54 -0
- package/templates/.claude/hooks/compress-output.sh +17 -0
- package/templates/.claude/hooks/protect-files.sh +37 -0
- package/templates/.claude/hooks/reinject-context.sh +28 -0
- package/templates/.claude/hooks/session-start.md +13 -0
- package/templates/.claude/hooks/skill-router.sh +1681 -0
- package/templates/.claude/hooks/verification-guard.sh +271 -0
- package/templates/.claude/settings.json +144 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/mce/mc.xsd +75 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/pack.py +159 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/unpack.py +29 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validate.py +69 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/__init__.py +15 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/base.py +951 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/docx.py +274 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/pptx.py +315 -0
- package/templates/.claude/skills/_shared/ooxml/scripts/validation/redlining.py +279 -0
- package/templates/.claude/skills/backend-api/SKILL.md +26 -0
- package/templates/.claude/skills/canvas-design/LICENSE.txt +202 -0
- package/templates/.claude/skills/canvas-design/SKILL.md +130 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/templates/.claude/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/templates/.claude/skills/code-review/SKILL.md +97 -0
- package/templates/.claude/skills/debugging-toolkit/SKILL.md +156 -0
- package/templates/.claude/skills/delivery/SKILL.md +92 -0
- package/templates/.claude/skills/discover-security/SKILL.md +86 -0
- package/templates/.claude/skills/docker-packaging/SKILL.md +60 -0
- package/templates/.claude/skills/docs-manager/SKILL.md +465 -0
- package/templates/.claude/skills/docs-manager/init-project-docs.sh +70 -0
- package/templates/.claude/skills/docs-manager/templates/README.md.template +50 -0
- package/templates/.claude/skills/docs-manager/templates/agent-roles.md.template +24 -0
- package/templates/.claude/skills/docs-manager/templates/coding-conventions.md.template +28 -0
- package/templates/.claude/skills/docs-manager/templates/memory.md.template +30 -0
- package/templates/.claude/skills/docs-manager/templates/onboarding.md.template +20 -0
- package/templates/.claude/skills/docs-manager/templates/project.md.template +26 -0
- package/templates/.claude/skills/docs-quality/SKILL.md +148 -0
- package/templates/.claude/skills/docx/LICENSE.txt +30 -0
- package/templates/.claude/skills/docx/SKILL.md +197 -0
- package/templates/.claude/skills/docx/docx-js.md +350 -0
- package/templates/.claude/skills/docx/ooxml.md +610 -0
- package/templates/.claude/skills/docx/scripts/__init__.py +1 -0
- package/templates/.claude/skills/docx/scripts/document.py +1276 -0
- package/templates/.claude/skills/docx/scripts/templates/comments.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/templates/.claude/skills/docx/scripts/templates/people.xml +3 -0
- package/templates/.claude/skills/docx/scripts/utilities.py +374 -0
- package/templates/.claude/skills/duraone/SKILL.md +204 -0
- package/templates/.claude/skills/duraone/references/backend.md +636 -0
- package/templates/.claude/skills/duraone/references/frontend.md +1506 -0
- package/templates/.claude/skills/duraone/references/sql.md +631 -0
- package/templates/.claude/skills/duraone/references/workflow.md +520 -0
- package/templates/.claude/skills/executing-plans/SKILL.md +76 -0
- package/templates/.claude/skills/file-organizer/SKILL.md +433 -0
- package/templates/.claude/skills/frontend/SKILL.md +26 -0
- package/templates/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/templates/.claude/skills/frontend-design/SKILL.md +42 -0
- package/templates/.claude/skills/frontend-vue/SKILL.md +127 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Box.vue +137 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Button.vue +93 -0
- package/templates/.claude/skills/frontend-vue/components/Control/ButtonBar.vue +29 -0
- package/templates/.claude/skills/frontend-vue/components/Control/ButtonFloat.vue +62 -0
- package/templates/.claude/skills/frontend-vue/components/Control/CheckButton.vue +75 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Checkbox.vue +58 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Datetime.vue +148 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Dropdownlist.vue +156 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Input.vue +106 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Label.vue +38 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Master/BoxColumn.vue +24 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/Confirm.vue +33 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/Info.vue +32 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/ModalInfo.vue +39 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Popup/Reject.vue +64 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Tag.vue +82 -0
- package/templates/.claude/skills/frontend-vue/components/Control/Upload.vue +61 -0
- package/templates/.claude/skills/frontend-vue/components/ControlMobile/Dropdownlist.vue +103 -0
- package/templates/.claude/skills/frontend-vue/components/ControlMobile/PagingBar.vue +108 -0
- package/templates/.claude/skills/frontend-vue/components/ControlMobile/UploadImage.vue +137 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/AG.vue +806 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/AntTable.vue +253 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/CustomDropdownEditor.vue +43 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/CustomDropdownEditorEnable.vue +55 -0
- package/templates/.claude/skills/frontend-vue/components/Grid/HtmlTable.vue +40 -0
- package/templates/.claude/skills/frontend-vue/components/PDFViewer.vue +25 -0
- package/templates/.claude/skills/frontend-vue/components/Panel/FormView.vue +309 -0
- package/templates/.claude/skills/frontend-vue/components/Partial/Footer.vue +23 -0
- package/templates/.claude/skills/frontend-vue/components/Partial/Header.vue +265 -0
- package/templates/.claude/skills/frontend-vue/components/Partial/Sidebar.vue +122 -0
- package/templates/.claude/skills/frontend-vue/components/Template.vue +16 -0
- package/templates/.claude/skills/frontend-vue/components/View/Form.vue +89 -0
- package/templates/.claude/skills/frontend-vue/composables/indexDBStore.js +140 -0
- package/templates/.claude/skills/frontend-vue/composables/masterApi.js +362 -0
- package/templates/.claude/skills/frontend-vue/composables/state.js +578 -0
- package/templates/.claude/skills/frontend-vue/composables/useRequest.js +221 -0
- package/templates/.claude/skills/frontend-vue/composables/useSession.js +179 -0
- package/templates/.claude/skills/frontend-vue/composables/useTranslation.js +54 -0
- package/templates/.claude/skills/frontend-vue/composables/useWebSocket.js +257 -0
- package/templates/.claude/skills/frontend-vue/composables/userObj.js +111 -0
- package/templates/.claude/skills/frontend-vue/composables/utils.js +322 -0
- package/templates/.claude/skills/frontend-vue/reference/composables-example.vue +320 -0
- package/templates/.claude/skills/frontend-vue/reference/form-example.vue +183 -0
- package/templates/.claude/skills/frontend-vue/reference/grid-example.vue +147 -0
- package/templates/.claude/skills/frontend-vue/reference/masterdata-example/[id].vue +106 -0
- package/templates/.claude/skills/frontend-vue/reference/masterdata-example/index.vue +58 -0
- package/templates/.claude/skills/frontend-vue/reference/popup-example.vue +159 -0
- package/templates/.claude/skills/pdf/LICENSE.txt +30 -0
- package/templates/.claude/skills/pdf/SKILL.md +294 -0
- package/templates/.claude/skills/pdf/forms.md +205 -0
- package/templates/.claude/skills/pdf/reference.md +612 -0
- package/templates/.claude/skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/templates/.claude/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/templates/.claude/skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/templates/.claude/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/templates/.claude/skills/pdf/scripts/create_validation_image.py +41 -0
- package/templates/.claude/skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/templates/.claude/skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/templates/.claude/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/templates/.claude/skills/pdf-processing/SKILL.md +107 -0
- package/templates/.claude/skills/pdf-processing-pro/FORMS.md +610 -0
- package/templates/.claude/skills/pdf-processing-pro/OCR.md +137 -0
- package/templates/.claude/skills/pdf-processing-pro/SKILL.md +296 -0
- package/templates/.claude/skills/pdf-processing-pro/TABLES.md +626 -0
- package/templates/.claude/skills/pdf-processing-pro/scripts/analyze_form.py +307 -0
- package/templates/.claude/skills/postgres/SKILL.md +69 -0
- package/templates/.claude/skills/postgres/reference/fn_get_examples.sql +208 -0
- package/templates/.claude/skills/postgres/reference/fn_rpt_examples.sql +239 -0
- package/templates/.claude/skills/postgres/reference/utility_functions.sql +94 -0
- package/templates/.claude/skills/pptx/LICENSE.txt +30 -0
- package/templates/.claude/skills/pptx/SKILL.md +484 -0
- package/templates/.claude/skills/pptx/html2pptx.md +625 -0
- package/templates/.claude/skills/pptx/ooxml.md +427 -0
- package/templates/.claude/skills/pptx/scripts/html2pptx.js +979 -0
- package/templates/.claude/skills/pptx/scripts/inventory.py +1020 -0
- package/templates/.claude/skills/pptx/scripts/rearrange.py +231 -0
- package/templates/.claude/skills/pptx/scripts/replace.py +385 -0
- package/templates/.claude/skills/pptx/scripts/thumbnail.py +450 -0
- package/templates/.claude/skills/repo-maintenance/SKILL.md +97 -0
- package/templates/.claude/skills/research/EXAMPLES.md +434 -0
- package/templates/.claude/skills/research/REFERENCE.md +399 -0
- package/templates/.claude/skills/research/SKILL.md +136 -0
- package/templates/.claude/skills/root-cause-tracing/SKILL.md +174 -0
- package/templates/.claude/skills/root-cause-tracing/find-polluter.sh +63 -0
- package/templates/.claude/skills/sharing-skills/SKILL.md +194 -0
- package/templates/.claude/skills/sql-optimization-patterns/SKILL.md +493 -0
- package/templates/.claude/skills/subagent-driven-development/SKILL.md +189 -0
- package/templates/.claude/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/templates/.claude/skills/systematic-debugging/SKILL.md +295 -0
- package/templates/.claude/skills/systematic-debugging/test-academic.md +14 -0
- package/templates/.claude/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/templates/.claude/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/templates/.claude/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/templates/.claude/skills/test-driven-development/SKILL.md +364 -0
- package/templates/.claude/skills/testing-anti-patterns/SKILL.md +302 -0
- package/templates/.claude/skills/testing-quality/SKILL.md +97 -0
- package/templates/.claude/skills/verification-before-completion/SKILL.md +139 -0
- package/templates/.claude/skills/webapp-testing/LICENSE.txt +202 -0
- package/templates/.claude/skills/webapp-testing/SKILL.md +96 -0
- package/templates/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
- package/templates/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/templates/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/templates/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
- package/templates/.claude/ukit/index/build-index.mjs +28 -0
- package/templates/.claude/ukit/index/cache-utils.mjs +140 -0
- package/templates/.claude/ukit/index/lib/index-core.mjs +2800 -0
- package/templates/.claude/ukit/index/query-index.mjs +150 -0
- package/templates/.claude/ukit/index/refresh-index.mjs +57 -0
- package/templates/.claude/ukit/index/reset-auto-permissions.mjs +76 -0
- package/templates/.claude/ukit/index/resolve-context.mjs +279 -0
- package/templates/.claude/ukit/index/route-catalog.mjs +258 -0
- package/templates/.claude/ukit/index/route-task.mjs +1994 -0
- package/templates/.claude/ukit/index/triage.mjs +133 -0
- package/templates/.claude/ukit/index/verify-context.mjs +689 -0
- package/templates/.claude/ukit/runtime/compact-threshold.mjs +1013 -0
- package/templates/.claude/ukit/runtime/output-compression.mjs +1340 -0
- package/templates/.claude/ukit/runtime/reinject-context.mjs +874 -0
- package/templates/.claude/ukit/runtime/token-utils.mjs +500 -0
- package/templates/.codex/README.md +83 -0
- package/templates/.codex/settings.json +187 -0
- package/templates/.gitignore +75 -0
- package/templates/AGENTS.md +116 -0
- package/templates/CLAUDE.md +93 -0
- package/templates/adapter-presets/antigravity/README.md +22 -0
- package/templates/adapter-presets/antigravity/rules.md +49 -0
- package/templates/adapter-presets/claude/settings.local.json +42 -0
- package/templates/adapter-presets/codex/settings.local.json +6 -0
- package/templates/adapter-presets/opencode/opencode.template.json +1 -0
- package/templates/docs/BUGFIX.md +20 -0
- package/templates/docs/BUG_INDEX.md +12 -0
- package/templates/docs/BUG_METRICS.md +7 -0
- package/templates/docs/BUG_TEMPLATE.md +13 -0
- package/templates/docs/CODE_MAP.md +35 -0
- package/templates/docs/INSTALL.md +113 -0
- package/templates/docs/MEMORY.md +49 -0
- package/templates/docs/PROJECT.md +50 -0
- package/templates/docs/UKIT_USAGE_GUIDE.md +147 -0
- package/templates/docs/WORKLOG.md +10 -0
- package/templates/ukit/README.md +14 -0
- package/templates/ukit/storage/cache/compact-history.json +3 -0
- package/templates/ukit/storage/cache/compact-pressure.json +1 -0
- package/templates/ukit/storage/cache/output-history.json +3 -0
- package/templates/ukit/storage/cache/prompt-cache.json +3 -0
- package/templates/ukit/storage/config.json +37 -0
- package/templates/ukit/storage/memory/projects/.gitkeep +2 -0
- package/templates/ukit/storage/memory/sessions/.gitkeep +0 -0
- package/templates/ukit/storage/memory/user.json +5 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { isSymlinkTo } from './fileOps.js';
|
|
3
|
+
|
|
4
|
+
async function readFileOrNull(filePath, encoding = 'utf8') {
|
|
5
|
+
try {
|
|
6
|
+
if (encoding === null) {
|
|
7
|
+
return await fs.readFile(filePath);
|
|
8
|
+
}
|
|
9
|
+
return await fs.readFile(filePath, encoding);
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function checkLinkStatus(targetPath, linkTarget) {
|
|
16
|
+
try {
|
|
17
|
+
const stat = await fs.lstat(targetPath);
|
|
18
|
+
if (!stat.isSymbolicLink()) {
|
|
19
|
+
return 'update'; // exists but is not a symlink — needs replacing
|
|
20
|
+
}
|
|
21
|
+
return (await isSymlinkTo(targetPath, linkTarget)) ? 'unchanged' : 'update';
|
|
22
|
+
} catch {
|
|
23
|
+
return 'create'; // does not exist
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveFileAction(entry, existingContent) {
|
|
28
|
+
const exists = existingContent !== null;
|
|
29
|
+
const binaryEntry = Buffer.isBuffer(entry.renderedContent);
|
|
30
|
+
|
|
31
|
+
let action;
|
|
32
|
+
if (!exists) {
|
|
33
|
+
action = 'create';
|
|
34
|
+
} else if (entry.mergeStrategy === 'append' && !binaryEntry) {
|
|
35
|
+
action = existingContent.endsWith(entry.renderedContent) ? 'unchanged' : 'update';
|
|
36
|
+
} else if (entry.mergeStrategy === 'prepend' && !binaryEntry) {
|
|
37
|
+
action = existingContent.startsWith(entry.renderedContent) ? 'unchanged' : 'update';
|
|
38
|
+
} else if (binaryEntry) {
|
|
39
|
+
action = Buffer.isBuffer(existingContent) && existingContent.equals(entry.renderedContent)
|
|
40
|
+
? 'unchanged'
|
|
41
|
+
: entry.mergeStrategy === 'skip'
|
|
42
|
+
? 'skip'
|
|
43
|
+
: 'update';
|
|
44
|
+
} else if (existingContent === entry.renderedContent) {
|
|
45
|
+
action = 'unchanged';
|
|
46
|
+
} else if (entry.mergeStrategy === 'skip') {
|
|
47
|
+
action = 'skip';
|
|
48
|
+
} else {
|
|
49
|
+
action = 'update';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { ...entry, exists, action, existingContent };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function diffInstallPlan(plan) {
|
|
56
|
+
// Check all entries in parallel
|
|
57
|
+
const results = await Promise.all(
|
|
58
|
+
plan.entries.map(async (entry) => {
|
|
59
|
+
if (entry.type === 'link') {
|
|
60
|
+
const action = await checkLinkStatus(entry.targetPath, entry.linkTarget);
|
|
61
|
+
return { ...entry, exists: action !== 'create', action, existingContent: null };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const existingContent = await readFileOrNull(
|
|
65
|
+
entry.targetPath,
|
|
66
|
+
Buffer.isBuffer(entry.renderedContent) ? null : 'utf8',
|
|
67
|
+
);
|
|
68
|
+
return resolveFileAction(entry, existingContent);
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { escapeRegExp, writeFileAtomic } from './fileOps.js';
|
|
4
|
+
|
|
5
|
+
const UKIT_ENTRIES = [
|
|
6
|
+
'.cache/',
|
|
7
|
+
'.antigravity/',
|
|
8
|
+
'.claude/',
|
|
9
|
+
'.codex/',
|
|
10
|
+
'.ukit/',
|
|
11
|
+
'opencode.json',
|
|
12
|
+
'AGENTS.md',
|
|
13
|
+
'CLAUDE.md',
|
|
14
|
+
'.claude/ukit/.ukit/',
|
|
15
|
+
'.claude/ukit/permission-usage.json',
|
|
16
|
+
'.claude/ukit/permission-audit.log',
|
|
17
|
+
'.codex/settings.local.json',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const MARKER_START = '# UKit generated (do not edit)';
|
|
21
|
+
const MARKER_END = '# /UKit';
|
|
22
|
+
|
|
23
|
+
export async function ensureGitignore(projectRoot) {
|
|
24
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
25
|
+
|
|
26
|
+
let content = '';
|
|
27
|
+
try {
|
|
28
|
+
content = await fs.readFile(gitignorePath, 'utf8');
|
|
29
|
+
} catch {
|
|
30
|
+
// no .gitignore yet
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const newBlock = [
|
|
34
|
+
MARKER_START,
|
|
35
|
+
...UKIT_ENTRIES,
|
|
36
|
+
MARKER_END,
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
if (content.includes(MARKER_START)) {
|
|
40
|
+
// Replace existing UKit block with current entries
|
|
41
|
+
const blockPattern = new RegExp(
|
|
42
|
+
`${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`,
|
|
43
|
+
);
|
|
44
|
+
const existingBlock = content.match(blockPattern)?.[0];
|
|
45
|
+
if (existingBlock === newBlock) {
|
|
46
|
+
return false; // already up to date
|
|
47
|
+
}
|
|
48
|
+
const updated = content.replace(blockPattern, newBlock);
|
|
49
|
+
await writeFileAtomic(gitignorePath, updated);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// No marker block — check if entries are already present as active (non-comment) lines.
|
|
54
|
+
// Must filter out comment lines to avoid false-negatives like "# placeholder .claude/ukit/.ukit/"
|
|
55
|
+
// matching the entry check and incorrectly skipping the block insertion.
|
|
56
|
+
const activeLines = content
|
|
57
|
+
.split('\n')
|
|
58
|
+
.map((l) => l.trim())
|
|
59
|
+
.filter((trimmed) => trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('!'));
|
|
60
|
+
const missing = UKIT_ENTRIES.filter((e) => !activeLines.some((l) => l.includes(e)));
|
|
61
|
+
if (missing.length === 0) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Append marker block with missing entries
|
|
66
|
+
const block = ['', newBlock, ''].join('\n');
|
|
67
|
+
await writeFileAtomic(gitignorePath, content.trimEnd() + '\n' + block);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Remove the UKit-managed block from .gitignore.
|
|
73
|
+
* Called by uninstall to leave the project in a clean state.
|
|
74
|
+
* @returns {boolean} true if the block was found and removed
|
|
75
|
+
*/
|
|
76
|
+
export async function removeGitignoreBlock(projectRoot) {
|
|
77
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
78
|
+
|
|
79
|
+
let content = '';
|
|
80
|
+
try {
|
|
81
|
+
content = await fs.readFile(gitignorePath, 'utf8');
|
|
82
|
+
} catch {
|
|
83
|
+
return false; // no .gitignore — nothing to remove
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!content.includes(MARKER_START)) {
|
|
87
|
+
return false; // no UKit block present
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Walk lines and excise the block plus any leading blank line that
|
|
91
|
+
// ensureGitignore appended before the marker.
|
|
92
|
+
const lines = content.split('\n');
|
|
93
|
+
const result = [];
|
|
94
|
+
let inBlock = false;
|
|
95
|
+
|
|
96
|
+
for (const line of lines) {
|
|
97
|
+
if (line === MARKER_START) {
|
|
98
|
+
inBlock = true;
|
|
99
|
+
// Remove the blank separator line that ensureGitignore prepended.
|
|
100
|
+
if (result.length > 0 && result[result.length - 1] === '') {
|
|
101
|
+
result.pop();
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (inBlock) {
|
|
106
|
+
if (line === MARKER_END) {
|
|
107
|
+
inBlock = false;
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
result.push(line);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const joined = result.join('\n').trimEnd();
|
|
115
|
+
await writeFileAtomic(gitignorePath, joined ? joined + '\n' : '');
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export async function pathExists(targetPath) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(targetPath);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function readJsonIfExists(filePath) {
|
|
14
|
+
try {
|
|
15
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function escapeRegExp(str) {
|
|
23
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveProjectRelativePath(projectRoot, relPath) {
|
|
27
|
+
if (typeof relPath !== 'string') {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const trimmed = relPath.trim();
|
|
32
|
+
if (!trimmed || trimmed === '.' || trimmed === '..') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (path.isAbsolute(trimmed)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const absolute = path.resolve(projectRoot, trimmed);
|
|
41
|
+
const root = path.resolve(projectRoot);
|
|
42
|
+
const relative = path.relative(root, absolute);
|
|
43
|
+
|
|
44
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return absolute;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function ensureDir(dirPath) {
|
|
52
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function writeFileAtomic(filePath, content) {
|
|
56
|
+
const dir = path.dirname(filePath);
|
|
57
|
+
await ensureDir(dir);
|
|
58
|
+
|
|
59
|
+
const tempPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
if (Buffer.isBuffer(content)) {
|
|
63
|
+
await fs.writeFile(tempPath, content);
|
|
64
|
+
} else {
|
|
65
|
+
await fs.writeFile(tempPath, content, 'utf8');
|
|
66
|
+
}
|
|
67
|
+
await fs.rename(tempPath, filePath);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
try {
|
|
70
|
+
await fs.rm(tempPath, { force: true });
|
|
71
|
+
} catch {
|
|
72
|
+
// ignore cleanup errors
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (error?.code === 'EISDIR') {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Cannot write file ${filePath} because a directory already exists there. Remove or rename the directory and try again.`,
|
|
78
|
+
{ cause: error },
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function copyFileSafe(fromPath, toPath) {
|
|
87
|
+
await ensureDir(path.dirname(toPath));
|
|
88
|
+
await fs.copyFile(fromPath, toPath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function writeJson(filePath, data) {
|
|
92
|
+
await writeFileAtomic(filePath, `${JSON.stringify(data, null, 2)}\n`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create a directory symlink (macOS/Linux) or junction (Windows).
|
|
97
|
+
* Junctions on Windows don't require elevated privileges.
|
|
98
|
+
* @param {string} target - Absolute path to the link target directory
|
|
99
|
+
* @param {string} linkPath - Absolute path where the link will be created
|
|
100
|
+
*/
|
|
101
|
+
export async function createDirectoryLink(target, linkPath) {
|
|
102
|
+
await ensureDir(path.dirname(linkPath));
|
|
103
|
+
|
|
104
|
+
if (process.platform === 'win32') {
|
|
105
|
+
// Junctions require absolute paths on Windows
|
|
106
|
+
await fs.symlink(path.resolve(target), linkPath, 'junction');
|
|
107
|
+
} else {
|
|
108
|
+
// Relative symlink for portability on macOS/Linux
|
|
109
|
+
const relTarget = path.relative(path.dirname(linkPath), target);
|
|
110
|
+
await fs.symlink(relTarget, linkPath);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if linkPath is a symlink/junction pointing to the expected target.
|
|
116
|
+
* @param {string} linkPath - Path to check
|
|
117
|
+
* @param {string} expectedTarget - Absolute path the link should resolve to
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
export async function isSymlinkTo(linkPath, expectedTarget) {
|
|
121
|
+
try {
|
|
122
|
+
const stat = await fs.lstat(linkPath);
|
|
123
|
+
if (!stat.isSymbolicLink()) return false;
|
|
124
|
+
|
|
125
|
+
const actual = await fs.readlink(linkPath);
|
|
126
|
+
const resolved = path.resolve(path.dirname(linkPath), actual);
|
|
127
|
+
return resolved === path.resolve(expectedTarget);
|
|
128
|
+
} catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Remove a symlink or junction only — does NOT remove real directories.
|
|
135
|
+
* Safe to call on adapter link paths where a real directory means user content.
|
|
136
|
+
* @param {string} linkPath - Path to remove
|
|
137
|
+
* @returns {boolean} true if a symlink/junction was removed
|
|
138
|
+
*/
|
|
139
|
+
export async function removeLinkOnly(linkPath) {
|
|
140
|
+
try {
|
|
141
|
+
const stat = await fs.lstat(linkPath);
|
|
142
|
+
if (!stat.isSymbolicLink()) return false;
|
|
143
|
+
await fs.unlink(linkPath);
|
|
144
|
+
return true;
|
|
145
|
+
} catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Remove a managed file or symlink only — does NOT remove real directories.
|
|
152
|
+
* Safe for adapter-managed config/readme files where a directory would imply user content.
|
|
153
|
+
* @param {string} targetPath - Path to remove
|
|
154
|
+
* @returns {boolean} true if a file/symlink was removed
|
|
155
|
+
*/
|
|
156
|
+
export async function removeFileOrLinkOnly(targetPath) {
|
|
157
|
+
try {
|
|
158
|
+
const stat = await fs.lstat(targetPath);
|
|
159
|
+
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
await fs.unlink(targetPath);
|
|
163
|
+
return true;
|
|
164
|
+
} catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Remove a symlink, junction, or directory at the given path.
|
|
171
|
+
* @param {string} linkPath - Path to remove
|
|
172
|
+
* @returns {boolean} true if something was removed
|
|
173
|
+
*/
|
|
174
|
+
export async function removeLinkOrDir(linkPath) {
|
|
175
|
+
try {
|
|
176
|
+
const stat = await fs.lstat(linkPath);
|
|
177
|
+
if (stat.isSymbolicLink()) {
|
|
178
|
+
await fs.unlink(linkPath);
|
|
179
|
+
} else if (stat.isDirectory()) {
|
|
180
|
+
await fs.rm(linkPath, { recursive: true, force: true });
|
|
181
|
+
} else {
|
|
182
|
+
await fs.unlink(linkPath);
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const DEFAULT_CONFIG = {
|
|
2
|
+
archiveAfterDays: 30,
|
|
3
|
+
maxSessionsKept: 20,
|
|
4
|
+
conflictResolution: 'latest_wins',
|
|
5
|
+
redactPatterns: [
|
|
6
|
+
/sk-[a-z0-9_-]+/gi,
|
|
7
|
+
/api[_-]?key\s*[:=]\s*[^\s,;]+/gi,
|
|
8
|
+
/password\s*[:=]\s*[^\s,;]+/gi,
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const CATEGORY_KEYWORDS = {
|
|
13
|
+
database: ['postgresql', 'mysql', 'sqlite', 'mariadb', 'mongodb', 'redis'],
|
|
14
|
+
api_style: ['rest', 'graphql', 'grpc'],
|
|
15
|
+
auth: ['jwt', 'session', 'oauth', 'saml', 'apikey', 'api key'],
|
|
16
|
+
backend_framework: ['express', 'fastify', 'nestjs', 'koa', 'hono'],
|
|
17
|
+
frontend_framework: ['react', 'vue', 'svelte', 'angular'],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function clone(value) {
|
|
21
|
+
if (value === undefined || value === null) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return JSON.parse(JSON.stringify(value));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalize(text) {
|
|
29
|
+
return String(text ?? '')
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replace(/[^\p{L}\p{N}\s-]/gu, ' ')
|
|
32
|
+
.replace(/\s+/g, ' ')
|
|
33
|
+
.trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function uniqueStrings(values) {
|
|
37
|
+
return [...new Set((values ?? []).filter(Boolean))];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function redactString(text, patterns) {
|
|
41
|
+
let result = String(text ?? '');
|
|
42
|
+
for (const pattern of patterns) {
|
|
43
|
+
result = result.replace(pattern, '[REDACTED]');
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function redactValue(value, patterns) {
|
|
49
|
+
if (typeof value === 'string') {
|
|
50
|
+
return redactString(value, patterns);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
return value.map((entry) => redactValue(entry, patterns));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (value && typeof value === 'object') {
|
|
58
|
+
return Object.fromEntries(
|
|
59
|
+
Object.entries(value).map(([key, entry]) => [key, redactValue(entry, patterns)]),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function inferDecisionCategory(decisionText) {
|
|
67
|
+
const normalized = normalize(decisionText);
|
|
68
|
+
for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
|
|
69
|
+
if (keywords.some((keyword) => normalized.includes(keyword))) {
|
|
70
|
+
return category;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function detectConflict(newDecision, existingDecisions = []) {
|
|
78
|
+
const newCategory = inferDecisionCategory(newDecision?.what);
|
|
79
|
+
if (!newCategory) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const normalizedNewWhat = normalize(newDecision?.what);
|
|
84
|
+
return existingDecisions.find((decision) => {
|
|
85
|
+
if (!decision || decision.id === newDecision?.id) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const existingCategory = inferDecisionCategory(decision.what);
|
|
90
|
+
if (existingCategory !== newCategory) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return normalize(decision.what) !== normalizedNewWhat;
|
|
95
|
+
}) ?? null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function resolveDecisionConflicts(projectMemory, conflictResolution) {
|
|
99
|
+
const decisions = [...(projectMemory.decisions ?? [])].sort((left, right) => (left.when ?? 0) - (right.when ?? 0));
|
|
100
|
+
const conflicts = [];
|
|
101
|
+
|
|
102
|
+
for (let index = 0; index < decisions.length; index += 1) {
|
|
103
|
+
const current = decisions[index];
|
|
104
|
+
const prior = decisions.slice(0, index).filter((decision) => !decision.supersededBy);
|
|
105
|
+
const conflict = detectConflict(current, prior);
|
|
106
|
+
if (!conflict) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
conflicts.push({ oldDecisionId: conflict.id, newDecisionId: current.id });
|
|
111
|
+
if (conflictResolution === 'latest_wins') {
|
|
112
|
+
conflict.supersededBy = current.id;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
projectMemory.decisions = decisions;
|
|
117
|
+
return conflicts;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function archiveSessions(projectMemory, config) {
|
|
121
|
+
const now = config.now ?? Date.now();
|
|
122
|
+
const maxAgeMs = config.archiveAfterDays * 24 * 60 * 60 * 1000;
|
|
123
|
+
const sessions = [...(projectMemory.sessions ?? [])].sort((left, right) => (right.endedAt ?? 0) - (left.endedAt ?? 0));
|
|
124
|
+
|
|
125
|
+
const retained = [];
|
|
126
|
+
const archived = [];
|
|
127
|
+
|
|
128
|
+
for (const session of sessions) {
|
|
129
|
+
const sessionEndedAt = session.endedAt ?? session.startedAt ?? 0;
|
|
130
|
+
const isExpired = sessionEndedAt > 0 ? (now - sessionEndedAt) > maxAgeMs : false;
|
|
131
|
+
const overLimit = retained.length >= config.maxSessionsKept;
|
|
132
|
+
|
|
133
|
+
if (isExpired || overLimit) {
|
|
134
|
+
archived.push(session);
|
|
135
|
+
} else {
|
|
136
|
+
retained.push(session);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
projectMemory.sessions = retained;
|
|
141
|
+
return archived;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function runHygiene(projectMemory, config = {}) {
|
|
145
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
146
|
+
const nextMemory = redactValue(clone(projectMemory), mergedConfig.redactPatterns);
|
|
147
|
+
|
|
148
|
+
nextMemory.conventions = uniqueStrings(nextMemory.conventions);
|
|
149
|
+
nextMemory.techStack = uniqueStrings(nextMemory.techStack);
|
|
150
|
+
nextMemory.activeRules = uniqueStrings(nextMemory.activeRules);
|
|
151
|
+
|
|
152
|
+
const conflicts = resolveDecisionConflicts(nextMemory, mergedConfig.conflictResolution);
|
|
153
|
+
const archivedSessions = archiveSessions(nextMemory, mergedConfig);
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
projectMemory: nextMemory,
|
|
157
|
+
archivedSessions,
|
|
158
|
+
conflicts,
|
|
159
|
+
};
|
|
160
|
+
}
|