From 0de541e8f677ea9b2b28501b742e17b559c800cd Mon Sep 17 00:00:00 2001 From: alwkala Date: Wed, 2 Sep 2026 06:34:58 +0300 Subject: [PATCH] release(doc): v1.4.0 sovereign brain mcp integration, hygiene auditor & fail-open protocol --- .tidyfactor | 2 +- CHANGELOG.md | 11 +- SKILL.md | 17 +++ brand.json | 2 +- dist/tidyfactor-doc.skill | Bin 58735 -> 64826 bytes dist/tidyfactor-doc/.tidyfactor | 2 +- dist/tidyfactor-doc/CHANGELOG.md | 11 +- dist/tidyfactor-doc/SKILL.md | 17 +++ dist/tidyfactor-doc/brand.json | 2 +- dist/tidyfactor-doc/package.json | 2 +- .../references/memory/collection-sources.md | 1 + .../references/memory/doc-templates.md | 1 + .../references/memory/doc-tree.md | 1 + .../references/memory/docsify-config.md | 1 + .../references/memory/mkdocs-config.md | 1 + .../references/memory/site-engines.md | 1 + .../references/workflows/collect.md | 5 + dist/tidyfactor-doc/tools/build-skill.js | 2 + manifest.json | 29 ++++ package.json | 2 +- .../memory/20-brain-baas-integration.md | 83 +++++++++++ references/memory/collection-sources.md | 1 + references/memory/doc-templates.md | 1 + references/memory/doc-tree.md | 1 + references/memory/docsify-config.md | 1 + references/memory/mkdocs-config.md | 1 + references/memory/site-engines.md | 1 + references/workflows/collect.md | 5 + scripts/audit_docs.py | 136 ++++++++++++++++++ tools/build-skill.js | 2 + 30 files changed, 328 insertions(+), 14 deletions(-) create mode 100644 manifest.json create mode 100644 references/memory/20-brain-baas-integration.md create mode 100644 scripts/audit_docs.py diff --git a/.tidyfactor b/.tidyfactor index 266f052..8099f8e 100644 --- a/.tidyfactor +++ b/.tidyfactor @@ -1,6 +1,6 @@ { "name": "tidyfactor-doc", - "version": "1.3.0", + "version": "1.4.0", "category": "architecture", "description": "TidyFactor Doc - Code Documentation Builder & Dual-Engine Publishing Platform (MkDocs Material & Docsify)", "repository": "https://github.com/TidyFactor/Doc", diff --git a/CHANGELOG.md b/CHANGELOG.md index 172b321..70fb999 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,14 @@ All notable changes to `tidyfactor-doc` will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -## [1.2.1] - 2026-08-25 +## [1.4.0] - 2026-09-02 -### Fixed -- **CLI Executable & NPX Packaging**: Added standard `"add-skill": "bin/add-skill.js"` mapping in `package.json` to ensure unified `npx @tidyfactor/cli-doc add-skill` execution. -- **Workflow Compliance**: Standardized `## Validation checklist` headers across all 8 workflows. +### 🧠 Added β€” Sovereign Brain MCP Integration, Hygiene Auditor & Fail-Open Protocol +- **Brain Integration Contract (`references/memory/20-brain-baas-integration.md`)**: Sovereign self-hosted architecture and Documentation Knowledge Item (KI) payload schemas. +- **Fail-Open Active Discovery (`references/workflows/collect.md`)**: Local workspace auto-sensing first, optional Brain MCP context acceleration (`search_knowledge_base`) when active, and instant 0ms silent fallback. +- **Runtime Tooling Manifest (`manifest.json`)**: Declared portable `audit_docs` tool conforming to `skill-manifest.tools.schema.json` with `"skill_root_anchor": "self"`. +- **Documentation Quality & Hygiene Auditor (`scripts/audit_docs.py`)**: Sub-second AST and pattern scanner detecting sensitive credentials leaks, passwords, private tokens, and banned absolute workstation URLs. +- **Tooling Scope & Anti-Triggers**: Enriched `SKILL.md` with explicit Rule 10 Tooling Scope and anti-triggers. --- diff --git a/SKILL.md b/SKILL.md index e9da082..34f043e 100644 --- a/SKILL.md +++ b/SKILL.md @@ -31,6 +31,23 @@ Read only the command file that matches the request. Do not read all commands si 6. **Zero Sensitive Data Leakage.** Under absolutely no circumstances should any real sensitive data (e.g., real API tokens, WHM/cPanel passwords, production server IPs, real DB credentials, secret keys, private auth tokens, or local absolute drive paths) be written into documentation. ALWAYS redact and replace these with safe generic placeholders. 7. **Clean Relative Links Only.** Never include local machine filesystem URLs (such as `file:///C:/...`, `file:c:`, or absolute workstation paths) in documentation links or markdown cross-references. All document cross-links must use clean relative markdown paths or standard public web URLs (`https://...`). +## Anti-Triggers + +- Do NOT use for writing marketing campaigns or sales copywriting (use `tidyfactor-marketing`). +- Do NOT use for platform infrastructure management or deployment tasks (use `tidyfactor-github` or ops skills). + +## Tooling Scope (Rule 10) + +- **Execution Tools**: + - `audit_docs` (`scripts/audit_docs.py`): Sub-second AST & credential leak scanner for markdown docs under `/docs`. +- **Contract & Schema**: Portably declared in `manifest.json` with `"skill_root_anchor": "self"`. +- **Invocability**: Executable via direct CLI or through `tidyfactor-brain` MCP `run_skill_tool`. + +## Skill vs MCP Boundary (Rule 12) + +- **Inside Skill**: Static documentation templates, stack-specific docblock schemas, and local hygiene auditing scripts. +- **MCP Layer**: Sovereign Brain persistence (`search_knowledge_base`, `extract_knowledge_item`) operating under the strict Fail-Open protocol (`references/memory/20-brain-baas-integration.md`). + ## Sequencing `init` β†’ `collect` β†’ `generate` (repeatable, once per doc target) β†’ `site` / `mkdocs` / `docsify` (optional, once /docs has real content). diff --git a/brand.json b/brand.json index 960f0da..6ef0b5e 100644 --- a/brand.json +++ b/brand.json @@ -1,6 +1,6 @@ { "name": "TidyFactor Doc", - "version": "1.3.0", + "version": "1.4.0", "publisher": "TidyFactor Ecosystem", "partner": "Alwkala Digital Agency (https://alwkala.com)", "contact": { diff --git a/dist/tidyfactor-doc.skill b/dist/tidyfactor-doc.skill index c848d25cee86dee7cba62874fa92b1051a64df58..9d43655cd32644354e2402b0e5ea12450984b440 100644 GIT binary patch delta 25950 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 VpLtnOI02jhp640!(cy2p{{es**}wn* delta 19935 zcmYiN1Cu69u(gf0ZQHhO+qP}{ZriqP+qP{R)0pOT_xJ1_=fr+1qAGttRz|M6R#x{X z=)*K9oRTak7#h(3EO?+KI9e!AGJO3q@lDDkK`KCx!w3`HF6b#C7@|1rs0X2LxxlUi zFpC>4&mxLFY7%)!aWaJXZ=Z?{nJniXega?g!AzJ+SO{`V8%^kJ6DWPY?1_65Pn7Se z(7yS1y-%fsrA~;^!$+pK5r1M~`Xtv3M2JPUnMZ%cyko_D^`lPO^%(B#0#`BWr1>>> zGCN@MT816j?&I3WrLw)L4GqGjZ(ApE{<*TAv&RhHCWVD5Xf8dpig$nZ1e=mj>fA46 z5^ddmF9oitu9fgcwy>MK;kukTLok^Cc2MN|0YLVa3esUF?edl|OF@vUw9vS4YjfTj zX4SZAy#Yuk1lnb5ILvpe(5QLV_*6qZzxB})g_#~ij-1V-_5CGib4SWTR za3hVcZ!|wY{R;&IB!HSCEQ<~hl@eBv6qi$!WUx1L*V=K};X?6ORQ~}^Q7gNxrQV*J zR~T2U*^Ix_*4EILX=bllMIj+ZMuv6*R%(7?BV;!0G;FQ_R^qfYFf$mGc)MDuzzvvU zaQGa#sTula|2iN9G)K{`6MEgZ8fWkTer`Wn0@Rty@C!78&&V}hjk5qNbgo+5&$KBb z7udSebh$9$IqewJQNhw+``nh7M1A#Z?&O{W(}j^mHug{HBajf+}Cv_C66^)L=F>3xUdNt@sz-p=y| za%D1{h&~6mF|ljC)_>^3(%JWu2Vv`2V8p0)aE5fqEm163*Z7(N4$d;qGAJ5$rmUvJ zJrS9{LuU)ybQWm_O|bf$X{R)prx#p4R0ApcVjoeO&4(|<$s~X$aq_wuEov`dDC~u} z_V{_?uNsA`S;pp%jCvJ2H@=y~@y&!aMzO2Yz zIL)sw0&_&o?-@W(je)+I9bbO|9xD+93)W3?Of+a5uLH(#Q_%RvQ_t_L#Ht_-bo`cl zY0lv|?NwldcXEiQ^jck$GrX!F^~I92gGWjuAyHaL*o5ZDZK(M-+DwM)-Z*p|Fu#9T1V-@LF?cs``$ewbI>u)ta`}-pNwX_-=!37x^37F~_q* z`@KS#(Qj$*bm0ITpfsPygyN0grkW}xUFKVMh^W5yYzb!}&?=Q!wt9MN;lgReO{1tm z3}ksIm;(@om*r+e1&Y1Nu1*fEwQ?PZw&`9Pe~g9_mxy@xqux~0NY+weE_PFgrXmBM zv)fsOBof9sQRtFxF>O!HHUA5OVULn4bGpTwCf@V#*d|LP5Tbz{IR0W6PA?=fr0kto zCc>wZC>fayVgV49exf#U^&wU}{dT@6U!3v<^#+iA7^#14X0tbHrEDC59$&>TYYGAL zJlh!lC~M{2yfihIAkx z-~vEr6nrBL?-;r9;_SlrzdL>(&)7Ye;KWZESdo?LH5O zM-10jXK>|}9}@_g^o+5GJo;ROx3cubn7*;vX=mMT>=j$K8O8r z`IUss+N$QXQtsVoqRMj2zLa8TDNb)=zYh5EC+S7x&|tNfSj$BV4$b9QV%7L4l(f~f zN#kf1Zqbz~L7&3W-6r^Ah*bdTVPuGyjr?t2_8NL{@lXybt!*2mVQdqFPg0dgY!MNk z$`oz)^LO6{XidGq^Cz{qhd!&IT|$lK$lgY7k(gLfW}wU8qZJ;oy0DIn zUiLsBK!fOiau0l6XScwD zRb>|(s*P}KY3zo_=ew1G8HYOu`T}hD;-ftJRji@@>`L{pBD?)b{s=kc$9J7az!|?n z#2LqO%}hcVbS2kcm1dIGUS%i$d+BM0;$D47y>>|MnF|kAf$XGPrU^5(@GWOe7T1;2 zZY4Ivxn{;`;lc;$dHA-!*)H9jz+{955dn2Hd8wjc-YDn7)@=^k)J|!|z79Bk2FZ|O zFL3GUn1SY)sPn9gB_Uj1#p(*J@p5E>Z`whIug+YeFHg%_Vi)~p82%#*8MvCEMu}yd zc+Sc;9(_>^m))6F8}}!n53i9ao={5TRJgu|rc%Mg9hQn8cXMeZ0`~Qp(Gl=Chf$9W zKoO3#*KZKTntijd>`{(-kpMJ7Du-F{CBod9ah3?`IW<)O>sq?K+I6o9Mb zRFQF#l4hJ=F=&wV9~GQhXbGe)(9v%LN<*j8CKxEjf+Cw+gY8pU#lbw4UbrCLiy!(^wjt|Hy5e!PuxrQNmC`(PKHL-?ig{C-P*N{ zLZVZ2RrJ8s)tAdZ*aKktHrECA$!uRsT;gALlE4WEP!(0(O0vE*A@*N|-xCr1NS$#%)E-F|N1HEkN8bPjziL8JSV7gClH|2W_P33@033fcD
Y%TSoypOSo!2YEah4>=8;D-ItqUPE$8o_$tGDxR9ho1IO%w#B>byI>(e2NZqBl`T z7rO%yeSxcCmj(EWSW3%OxruZ25d94l<`6o(GqNQgv+piymsRVRq6+3 zfboUYTaUx?9nWt(U{dki&@B{bBMB6jH=y0#Qp9MD63h-`5l&GzhTknWMdxw1%h=Fa zP`30(``kT*x?iWnV)u?A9R+6z&so>9SNOMQy7|S5AcNn|&N?t>rOS|R-t*hIW$c9a z28pGMP8G;X)&DKnG7q08jBqD_t#4(hLb?bE1cZy6;;l#s*m1y}LiWcj1W-*PqF}p3 z%5z{|%BS~g1HVcz*>g?FmUFL=`EISuxxGo9*Mpu?6C$0$zR9{&`&e+LY6ik1_Egl= zDCF^|pM3ZQ1e~jtG*D-KHfpFp85?|E|EpbZg0U!w_1tiCzOj|pa>Tt$J{H$$8(-&1 zbYWksYj(W@aEx1TchK4%uXn=V)ydcr;&l6T^gOGVdeU4OrKXAYnyg1i3j@{_AnnAf z{aO%xYV_F>vK?7GeWab^2Bu$bU@ubNjlS%Q3j zUC@|7o662h=O2n#UU#BuGpMSZ;2n9X{>Z7-!)L9Q+7{^eVlYionl8Ytvt0{QV&$pngwRR{Q`5 zYL)K;EMfE8%eQ^6X>KlWe^&h9p9Fw;IloAh;1VK$_k3KV?|FDVFEG9^lfaIXOI*ti zL{up1rfpK+psPHCH;}v>F&6Y{S$JUygEX-$ejR>%!s;xF*RJ~9+2^GM0N0!)3B?FJ zz$*AIt&%^y4V|uaC5Ckbh?>U-%g?DU@)RHeB<};**N%rJfWv{z)VjfbbT+yz-XT2x z{T1)v!3en9@;bTH?1(nWl{|pR#PNQo&O|yyQn?lF}k}ljQ%gP zL>Mc`Y>3)L`Bz)}5v8@E`P+vqA(<5fM2A^ga158%1heo|Vo%NW1(_<*2pn{0=GZUy|Y}5gboI7HfV2EnkR9n;eW1K2j&;{ zL;S43UWt;|W-2Q!Owds(bb?$gR780F#~r-H~*;L1C?M(=6CdKCvKE5@7R!Qx z`QwS?KyZc_Tg8Ks5s!cQ`O4K_COTLBZk@1h#8NIcj3Ki+P;O%QfFGXLOB)J`ZhZ#f zOneDjwXPpHdYWpinW^_tc_ms^`u)zhQQQ6x)VC7rqF^WyyA4~wvv)G^Ot8LoT`80N z_G`SG?lU*oLSS+Qv3p;jXqRW2{iCt+uqb-Nm1Vac+RB(<@sW4*LvQc+@;m2uP3n#| zfJwCQuj^YVDtijO4!U-SY+_6cE04BITcEixF?eNUlMwEEBl6%FAI zI%4XiO@1iy>_ZP=8su}`R4eJUB>QAu%NMVwOqu9Z=b`PygOofCy#vqRL4L;Xi6x$z z-;~_FFxP`}xJUhq{{m^Lwv}jZIa1K*3&+vNioqoYB%}ghr1%^EDkJ+-ic)*8rt+du zgAWt0|4Y?YrLm+t8llFcmdc58-N$H!A2{Z*Tsj(E#;;im1f6qdwyM+J4o;Bk)U9yV=JGEt^k6^3tLsL z>tP6AlJ)>}3`35;CB#0^X8`p*biJ>0LH#vULYsKC<0-Z@#2s6%6fZsoCbPRd`RbsW z**rFt3F_~4?bD)jXuuR#Zq%5Pao0b8U51=d!T$PSkAT}2EWp)sz>9;STA-{%O7RWA zLjwx^)BYtewez91BEnrb&^2#O)j5>DJG~Ccj?x6Ey_9a~uAH`)`HFPnN|hEQp!cqI zL9b57l=*q3xytzc=eas{y3xdJK+KI3I>H8ycGww9xSNp-gPTY35m?C!;B$MLPY7u@ z^!^}(H|K@*o;(0?B{r&9xIO&^{(spLiT?jWrvKl8`d@UjHuG*#(oO*TU*=T*9X^wC zq0a%Lpq@f#u!gYvA7P1%KpheQ0RnOW{~yAVf^Uof*oj|ZLh^4|1vGvJdVouJ_Nqf6 zZ!6JkHy3|}Be~p3n_O=u-Jyl8$@qa8PG%TUS|3-U_j=FgaufK*e>3F~v7vSbZsE=C z<4D|~p^~bLk!SJSHd88POCLco4r?AgBa|0LnUr0rytTjht460M7YYspl!TQY23G?a zHf{fBMx}kW$%W5 zn`@T^GvnVkUtK~!Bh=J%_cLO`eWYnCB!lDQ}rY-Mms!;&z$NYEv+Xo=kMuzh8XON13}mOy|5(g#WSwY zlrK4T+7Zo&#iv;s^yQ$e_Aa?09&J^|hu*Zj#>^gRw)P&rEKQQeBD^|eI0`fbI<*;- z_XMs^7NmQ3DTp-$sz9mrfQ1c#RsG=Qddt0|QqN)DS>J{p5f2$;ZU5v)exZD}4F_*a zPXW^uI-TQ6WCVj4H}piz-*ZnuL@)ms5R_ose|eRsOkABZ{=Zi;#nqGp1RkiR#xw=- zznCKA%8x8Wj;%jzd^&^lpG3Wa0|MemQO+Ps$@jqo{73XAk^MI`-PWtLOp*erwA)K% z#+=EzYqgrSQgiqGNx}z#uwVy?SV5Jgs@h+1Rlf28Gnl8SEA?azpV^mq!m?n)zK~`w#g*)q}}#q$au19FAs)*5{Z>_0|{T>27@KGV%R)s zo3N&8>ep8aHeVMAha)}h2+PzfH}$KRq4BEvq{5aLsyUc=o9zX+=j})5rF01`={`4s zbWw!{Wl3UO1rdtQk8({&WD&g)R%x!Qq^D|t3iX-(i7>V*y?#{-b6HxWM=YI=eeUe! z-#R$;hl_2XiS9LcN>`z}C3`#CJCk=iJaZP!I1^92+ZYcNsH#@7JW~27GI8_LNgo<; zCCsdUz7cTNy7_T|>ZIRc?m{uI5g9_FIt*PH2m~whSOGTbOHdow-bhaTCVDqI+%(Dn zTAqbT3h;Yc{YA6nk{+Kp*iTbhy^o5}PAW3a7fhqG>f~3x7dH5Q8|yl%(!Yl+Z0K6T zCGJ}Z<5HVROZYkz9pPVsr`J(UD}r99f^7Uzl0`I4BFfL=el0+H9PHKZ`JM`%)>HZi z=PvUYm56|efS%v}EAfW@2(g>07I->@!PHM%(q&l+{866*Z@WY&lD_ti zH14(&-cqqBNL#63q8&ln5V&y2FS8<$sj=COE|>u6y(tQS(g?qN zL*nTEZDuQDfIKsiLUXKinFw8qrh1wn*1Ap)8YXzltFp5TB2^WFd!)=E0@>V!mEf@9 zl4b&HuV}VSs4L6PJY|YP{gMWk#P=S2Fqi@BPvGP6(#5ImQl*~BFktbq+uV*eNv3di zcPm2~S=odz9U)F{KH$@dbs%3pA^sq@_l$!8pFlEV$;}5(9x+AOeP8Say^dm1AUj4M zn3z!QJNQiP?E4L8o9stzevf-Y|0n8;YOG*5hn$m2D$y*LlAuU8G{`lgG+(#2!;k@F2v9a z@r?qbR3OI23enXrAY>6T_OXPJ=W7QsK2nJO3Dr~x>M5}p{dIk@`O zX(mtwR}h8J3IFmY$#cE1pKu?&AF!7U1O80oJYp9^Q@(uymq0+$sh|r0rCT~GJh!Mm zGMH1nk1cc=_AS0nR&;$iGi z#+=MfibI^XxzGeRQ6b&a-bUg!nynL`Cz|-vD z=;*|1ijCvMuyT>Ee-#!SG2e(w4^RsRjl0#xBVc0Z8+mBq`(ie75d<(!W!VSpu3`6o zJ8V4u8UFu!ClpYaJDUESI7LAvC=k#xM2bQNWeR>U%6~9tM*_+JLQ~GT&|NL1`NRNT zBv5rpI*|v1uFVPw?bwGKrGvtAYp+ZV)9+I91@?xzHh3(SFoUSmD+%N=Sh4jR3(~6cVYAU}Z;|5LV5k*-TcL5vm zXTcCK0+3Nhr-G-owV5odqDd8~Fx98(^N>cv#~(VorPtCc5~oLwcl}Jqjgg2`WxU1t zCaa41CysXJZ2;?Qm{}JePo)2nLHzBJK_FhWgrzA%%2sDznV9l(vyA3OR$GGj0Gk;K zvRtDf+YsB|v~ek`+Kb7)tbV|`+c1;{*ChJ9!XgrI*%2{JIBN+cePlT@SY znENPMlG6%)vf8skb06$n914cqQeGP9m`2fnRCQaf4z~?txIX3zcrmTiEfrxq(i57N zlN%0PIZnjx@1&~R;=vNNsB7}~x+MJIcj)v9{%`{+QGSge0!gxwvn^Anr?JY@A~gaC z1BBHiQUfq1M%6-wBYtvT>s3%RMqb9x3>Npiv2%9b0Cu+#x1P8xEu-jo_o`9~28VCha13k-Im> zPnwuL6eZ->cu4kZ|7mE&+g9SMq?2&^0ibbBlDU&5cpbsnuOPg)G>~0>F&QT z@^PIM%V?yVjOky62gZg%A)9Kax|jxhHUK5-r-Yrgj!kcpT<>QAk1`ab^6Fu}KI+7% zavq4r*z}0%QoocR?j&MsfL+u3nes9YKylipCFlhPg;W zQrFYag0LC6+0Hmcl3R!NJ1xY$b|`U@WmOn99BqX0n*7dx$Nax)Hq!vn4@W8AA^oqK zTOfdd=>AvpGK9kec5HTpkp9!Wa8IJBjc1;yAZ6~vh}N>tS};8ZakcWL`c1qt$K^qb zj(V&{TN-KGwUSznq=}Rp zG&cJ0Y+kaFXe_WY*<9kB+9g61P|h2-139wi5eJ(S*{tRq`q)8nxU23#WQuAwl*$)5 zKK=-8Rhz&Ao{6t3#eONzpH;^d1epBDZi<}RA(kLxsI@}$@4~-?m+O*@-kQNpKa_&y z%;B9GyH*_~cI-R+hhiAslXcyML^(c7|Faz`Q8-KMu+ziv(b~#Qw#50Ceg+Fu^Y1_& z1`L86^@-8@h0VRX9k#%J&^aj8Oz_z1Y-=a@;cd|XU#W#6Q$KLn`a}XYiUL8V5dNRD zns+=;Q28?>9PFoYW|OCp5WSB~Su7N9lx{C7_bF~M(Vq>+%*sGXcw2r0}&11xP*OML_Qq9q24 zdEc?ebC$gD)=ji`9G zvtQzA*V~@M1J`wl@SjrF!-&PGMEDN)r12$Mj!hU7#c)XZa{K59ZnUq*K$6K9`w#>* z>>9$NcVeCeqbvGzyrjeik3u{Ch8xoDiPN1vM!WAEhp!V49Ec?3U!w6fUlP-D`7(GB z%FzrUORpWC|0FTu(6oyp|5f#MI}IvTlKHbQs96r{bAZ$b{-6J+l-9jL^zW1Vo0Q;z zfLh6cfLQ*wl)9tv07*KsE?qrj;#(}_UFqgi9>hD2 zf4o+KGkAmlN`{Y&2;i>T_}->=J1Hgv7fRy@(>PUf29K5)0W4wdu%6yKAD)Y#6TCsd zKyb4Wkt7@AGl_J*DjBm2?m| z3U2q&+pA2sTU%!b%S(ZDw>b@8D#`Q`{gqAhvNfLE9o=!cf_AMeBDkhAgC^}K#yeiA zv)~^LB7%Riqz_pbHjS(rXd_BaQVf%%ZvJbseW3Fv89;`DKjX5y=trz~-IB5iXKfOu zY-Enmz0&Tt-m+*3jJ_@{BQF_Q({f+(Zy3)>;16|FCKEn^)@0G&69MQ8gy7RoF zLD~CO+_*U0_Xmp-XZjrf$nA-d2#;=T6X=xVv?oo+jm4nDb?VsJ0{C#%(LH3ZK`q&s%3tHKr6E zT%A?~I%@Rv>&8;HF-C|ek^&r)P++{(qk6Uz(qp%^XM*~=5TYU`usCf&Ok&JhY26MD z-;e*2HSwQ$QKT!iuL+QJG3|u#1el_96WI?91M7951x2Y~vwXsxtyAlV zrxxk+22wX*M4^}{LPaz>YD^(+4!M7|G3#XCb z=)U^JQ-{L1wK*O}A~H=L0z>;)^9jj6luHL+70_mWF)g`ug(G9im773*Mn8Bjp%4_= z0PZeZRc-@VJewz!0}DaP+^!6*+?5_4=;4qOl)G=&LT2XXYgEH5}KEBzh3wU!JCSGu((yiR)m3*f3ZiNaoFC z)RfG&*eCQ1Fv48RQDd)nQ?7aMp@UM@0YK!z>y)$F@%bmu%`_T31cJgr2URaKKCYH+ z(~Mzo_)3AjyKK03X|Ve&Uv@8U|IQoc4&hE#E7b2(M#5)IuB}Lorptu2gx^WfrQ0=U z`oO8L@FI{3;6*}+8KPh-A;6wZ6(Hm?Ww6Oen47z(Vr%TM6TJq9ue)T6@7YbN0o$^b zB4LU?&0<}PK&(*yK}>7Zxzx)xYV!sTMI;|JTW{pK^KB;3$(;wJxB0U(xp*b6O8bfjUe&Zn1}De&NNFwE@M=JI9ypw=QNI$TOXaS)N!pu4a|xMcP3(OJ<~G#YO#+ zg*eK^qm!6V=Y*%FPl@X_W%R8cdSS%H{hrpvuTwvZ6=s20aP_pje^0i8>uas6M0I=V zF7N51EF|A~cWbr5tSzj%&+H!F!p>w56W{sWTt@iB+FJ&mrH&>OU|kMZ9bMTK-Mt|; z^x*H9xc1p77H+2?rq$Y2A-&H8oGWJmZRymoL!a1I@2Fk@12s)1mR32}#2SBs!ksL( zm-S+eMd8XHo3nYnbxe2jj%~BH4uj^c*5;A|d0E08`)O2McZ$k<-53+3X4=uvbjOW{&Y19t5Zhe(WW`}vwG}p4_C=t71kzB+0D<$$dZ}+Qqu^px3M)OF zD$y3bW$6Yqjd?f(5|4TxChEPvIB=_77N-@F_QT8ROrR83hAGS@dSrrnJ{5oUwZ)lH zhhUv6thw=qh{SGS`K?|ez4V0{4+*@N-X{34#nqlae)bf@o#FtNHK_ppUd@TA`UrlZW~qJ4DxrS zZBoGGikHr74k#^3T27xOsiazqyMC|`tKlp}Bd;KZ$bYa4#qk5c$dr#b#Ivy7*K#{G z0h0_k%{O7@zUQGxd0BrE%QmY&16@;b*sB-(S;3YvP(D6#Qa!gA7|1v%?g=@{A5fKG zrgHNo1YEIf+Nlv4kTaB+vh-g^y^qIFmrllKi^}8Wv4aeZgKo`md~Jd$Adk7A_~l*& zM(D;`FeIj9sz_kj{bEzQT0&(nsmHVU06Oz_yBx^Nok-)r<0(A72k`H(6y@>mi>16+ zsL&wR2b7?N*=Acm=O>2#QNKULEwI184So_!Z8_$PZE*gxML3#W#Xa=a1s+K-?%t|J zX@U4~LxDOy_kr0o-}tk?O0qko_d@jk96vVyZ2nzsNiLAbSoS0okfvy(Wk%{~1MY|X z-JxNg!WEyCA~VtA8{ZmobkQlb;)ny*Dgj z)i}Q`uFKH(Fbwe3RTKt9=;K!5zT#oQQ02%kI5ekXQ{-e zYSivYs~bWGZlavU2sfkv)4#k4E?w;d#O39(5<7_ID3>SucR_qV{;Xf%Q(%b0a3mM8 z|2D``;ht1YXBk38r?s64pvh%6FUiN zc+os%Qj?Q%kZmZkTg_I=6r#fpD_d_sgYQ~*sVU^Lk*P5Z1O$%&xyPRDmmiZCq+55y z6Oejjsxk6Yg-0=EQp7T4wuzEX3hes)3A#}E{|^b@vJTndM-BAVRY%)jQUbhPb7lF8&4h%fPk3)*J@7D$Y2H-y6&{o5Z9E@9siwZ0T<_SPIV zeNR(0w`);cy1mM?$SYQzNh`?|+or{ru&zTjPK(d!qVun-(|4Uk;Az87W`PY+VC9lv zF~k6__L1gn3vIKZG*If;#)VX5^)@9f;YEj~Ol&7)E45XfF2Y^w^#iBUc zHtESGn$Tk>GpJu$g&Hc65o?OT2*W#s&d~-c7bmKGu~4v$9xq^F6?HbGzoAaxvWtKt zx_7cXU=Z8d74^olhE8S>yaXj>OdjG@o%>rfOy@e7!iyIfrd3DnyKUE`x-?Nqs39;B zG;`A_PB-DB%+p|+8*e$w!zEW^s#406N-fn&)rpXM0&ZmSt4V5>Bqn0?1+Xxw`;G96 z-A(8fm7FN^pG&dge|bu)h7q(f6p|G9EwP2v9*z7S*sjp7_MvwCI(n*_!oK>n>{;!pj__^#v_ZY>S8M^-)A%`; zJ~>os;~9PtcY3x^^0FOx!0hev%1$@qqbR}4n!1d-Dl6o8B6mcj%=@74stq1XRVp}2 zCaMz=5Q*MP%d!ZCb|!-e`qA#v+;fAb)PLR4)|!F&9<8r>ua+IW<$#r6RN$BvYz%F{e6Mma$uP4f9MXM;E-N%+~R*+YGb;pIIv zph&61>7YL2uG>Cm(LQDb|Kq2a>FHgU%5+22Y9kmOOdsH4O(hvvlqCC!WF>JVOUbbn%uAC zN%lexM%XO?{OAT#Yfj0ae-SvaS*^0wE$u~?ih0YWFrKHt)`2EEj1vY4N~obmKPwT^ zpu|A^@|2|69d2bzF73MEtw%W)DrolS71)iGS%{5e-af!$&=p*)_iol%7}-CK=qSZ5U)HKBL4VsKfk3Y0-bnq&|aJ9fJG<0U$rn;GiuM&W%q9&!hg+4~ss8RGmM7P0ZPH!1g)6lH{p_bM?( zYY%H&n!)HdbQbW>Nr#Q=P#$stm_T#ub|w9e5fWu^HmS&UNMI9&eJ30Q-mWMD+Iz}) zcvQxqm5W-FOMF&`I~iAhXc}b9KJ~&dENFQ z*d`DREgTV1f(onOz@Ekt9G)5CSlN)Quml>@)Vsz>L;Za(%*W>UNg81a+p2X-#|eN( zf&m>aY8ZNMgi8Q@s>4C?2mcqC5cF;R@3F7pL5RMO+nWZr&eoJ@avn-n zN0apnU8-5Hz1cFO{*zG26}tEzi2rLd|MO2y(B^Lp^M7q7H|0+rH$dL;KliH-_YVMz zNQj8G9E~z#I07U-S8+1R2HJW_wu?mac$_R_PhY0|$n;m(u@CLr_8a^g+P{U&Tw59; z+1!QO@qDhA(@#WG4{IlPMbEc<<4h;x*2>8mr9uZ zb~S1`ij$@xZKJ$Y1mIocXl+hcMo|UJ;O|>fozdly#jr7oA6!#2t+hZ+j~m^mkdr3U z+k~7SD+%&=DxHBKFQ-ghgHNIl4%Owah^k&yHs9uGCFeN2;->if^LN2`oGnw9L>j8p zwVjgd?cf0LBkZ3QB3++k&$dCm;^z?rDim34^@con^kK8E}n$sCZMQ{b$y^E{euZV$eB|D>r-lOo99$4V*FZ7Xq1LWY1VXM;5!#j9l6 zD18cf*!A;Y7l7OAzb)#qwE9F6q32u}wbaha%g#9GPtEr5bmI7Dy}ac-Jt>~%si%jw z^jhB0j~m68R0<7y=MC`QmWzdn_r=&dm|KBr##NG!gB3MQ9SMuYFt-s2%ePNkt%HW$VH)Ej+T&? zCLj}Y9Dk7>B_%SB2w%UNO|5y>olcm^g{EDCF@drh0libkY8lpaH;RUmwO%&18~iZe$j7vnXU|@o&cSB{eQtZJhT2+yZW>fkH>l&>{HvJ*JL^IfGk_q9xx(D$kUdqY%Ehvt3D00#c*LG~+o%o(7i=12hXc4fCJ3~Lo9 zkQr3Ccy8+u!--aLUm{vMwFyb{Y_qF@(O2o!$W&mdIcWy-&ZH$-c@2hfM{2MH5Es$U zaMszH;qn0dz;>?vUXoGo$kM8`LH|+fv#v(zt*jR`+~nrv^5a71WHHANuqgNO5F#Wb z03xZ9*9o-z%JEK(u;Y};U+^SWr>vq8PyKc_CUNBg%8yd9vMpA@bKP49aX6&fTHKMv zIrZ=M3)OnW&Wkt$z(+?#CKI=<7`c_c#AC%uzP^c84n^{1FMXKBSBfCDW!Uuf3fu_D z_2s$6nVhpCJnYz87aRzQIE_EjaP8%(0c>CpJO{VED!DZE+p@}`wPldZjg10KHbeS~ z9&mhcau#}6jSiCC`v|I9`F365wN)d~p@^g36dHm7tH@Z=S|yCHI@>XqH5xLia^P7- zhDcpd_l>Pbjcoq@?F{1bSZ-H~^RXd!o^26${cB0d*!dMa$w+)yF*@gWn=w%pfOWOB z)QWCyK<{E9BxnN~@o32dLMevt%4QRfWAM$bS!>NYGEU4I6kZa>;m}>cXd)7bwCE^_ ztWV8%kB@W<)9#a-?#rov$FCV;Z6nU^WfKUdE-F3KiyOKc)1Bnalkz<48M&nQdD`RF zy}5K5c1&yRCIS7Q=GDIY+hKut0O@G?boqspaCd{uN`EbXZ~NRu0mjG?cVAW03Zun? zF@Py5EmnYJ(0FyfX5RYJm4qROMN>JN9inUIcr;?H_!77jbG`6xDU&&dMQa@ku$=rV zp?gsx9WO7NNAt0NcLP{leb%_LJM(iyI2$g7@>gp>Q9cb zUlu0@jPzIs*P5e&m|LD;wvOKt=NViz$83!~jh~$PmYsevq&%dKpvQd2D(_#aL8!e7 zgAFR|WG0pYKXkfp0!*LCK($}69ky5K7)z7$@S;z=s`SEF*rvF;f}}h>f}uC5@*tD= z=oU{(L>-GT1qJv^zI<&Y9eepYQH4Uc-VWgW5zyUifp-Zv2dw%u)m9uCpl|DB93%|I z>EnJ4PUpk6BS{LV_WJynVfLHsNdNEk@;{%jTf$1u;r?eF(Y{Ftd4>#IpO|i{eG?oA zs1gaSFk9!O)j21UF#W1Q@TXMgi-Mu%k>Tb{Q#uU3q5)8&u<;?bT`l%E93L{R zvs{8(giUgsTyYv7nwSdTw>(5zm#lnnBRgb^6uqrldJ@wwLMoPoy+PfKg}T|Ewz?ve zJu5T8%>XeSbG62zg5&-1-#PmjF%b%ZpHOW5Tf9C=1&>>sz`1V_fRKx#dlcDQz>Yur zHUy9`KiPWf6a_$_Nw?KpvwJ;--Xp(1vGd^NQdtMdk2rZc znsk2=wc@fET$R9%z##)%)fD&ST?vm+qj*~;s3b^(X+fTB!bIZjt%y}WJi1M-mFpCm zd}36zQ&5nRw`q=7vv=Dpk$PeI+@wK(Ema9P_;*jIasvvuGLT$tDuTwGgV2(Rf_*X) zM?9hIQHX9AxS2e;V>RAjtS2l5{Knqg81KW~3pU@rMZ7^vte@pi%uH=8Oo zWW=d6lZ=g1E8{8r@qYjfO+yd1tSTp5UuQ$DCxNhE!Nbq7c;e{MW>2M z;emBX1OXteal!s``q3*!O~-l9glKTdDM)4Lxd&Fhdd47j#%8QLSnU!({tLR!*-^^P zG4xV(X|$?&^k$7+uC+7=^d0%{LdaLaR$@*^%=2!;_aq{7tN>ah3#u%7nmFTcqUBRQ zyOxRVdKWSXaJK)>c$B)2;3%G988^G`8Aol+Rs~=g3oN&g=`(WkPX3Pq&@>;9QQF8< zAXZaQ3u>$8kW@}#`C~nM&hk7+V*3@4jgr!WLW?b&WOpid1%E`X*=}Hu&=a)*y~5&H zX=rvGj1CB0QHUI+Ht21M+W*(amB&NXz439Qh%DI$nT)8h?~=7rNJ1IOQub|Zm8HZC z5!s4s%f6(%wqb;qPvLz%BF7bp;atki8p#9?MI&!S#%+mq_1hc<;}@6gsU_*`2U3!R8Ie-Bgg61Q&akZ|~pQGrRj z#ZBXzUb*&zhpgCCq)>%vL1IC>m}`F=xOT&G4%Iq&_mDd1`pC}IzE}^j5SK^LcbJN_ zaI}E*#=xZS<)I-t-E<;(MvSk=w|TBuv$bDS%*O#>YOZT_K7W4j2)*?5r#~7m%p74h zh_m2zZ^YnavL2E$25$O6usHYER$3{$*B`)8_%LaM&oWUyz5T*Q%yho|0_Fj%yAv4S z7RUZ~My#-iRSexIBZKOpTSwVBD`4lOMPU*z_PtX>K1M4;trQ&QUi>W;#((I%x%eBM z61xt4E3Ft#`X)!e#Mqlm$p=(nrI~ZrzC`#{S1jrNsZ{#6W7rbnb9Z(1B8-KbYh`NeNM??mfV=M8uBZ*bw^|X*704d&FYHp^9jAF0_K7e3{PCm-x*vrm#0n4$#|9INab zI(%D9P+5IK!qZbDqT&%<(g<>lk;Esy0bKrk^Z6@MT28)~x4gFDXY%fIT{$;{3$CD4 z6(I_pi+L)orBW{3sYh%xihOWT?{rLYH+{(YvTFAJJ#F&5JwdJ9Y@X{|wgI_@z9LWf zLz+*l(1e6@uOPf3O%p5tgq0)LbMHB4i5ky7ZAb4Lw|f=b%f*;0rSL z8#KJEt*GUwu+r0`U&?KbNVqwr_Rzb2lv2nz^HKAP`y%;ByVS$DRfCq7>I<0T&FNM< z;kBjQ7v+{ZLn|XAJMMZ+pk(=SpUMk(i(FNM6J{LIhIfuQJ>6%-es%1D7p1K&MSROE zuy9I`MdN+$mRxkkxrK5qLF*&yJ#LjN{BNyi4!k{a&*Ogn&OihA(6DyfO_%YLc=LnQ za9>QP1@ccPJoL!osm!O|5hQmfL8CaHw;F4(MLxpo_Z#VI(qLp_}+VRsiX!? zuCBqcZgwCKm!UW+aAxgErH?)&m8O=Y)utiNn#w)p0-YSgEno38s$(@<`r8=7778>~8HqNy!XZ#J{9s=+ZFhk(d#?2h3(6 z0aLb)2h@v^LXt?MyaZ-7_^Bwy^2W!fMJh3yMG=wQ{?1i#5~|1m!>|`tnyy=CIeIce zKF}Yj}51Zcd_a2fU4Ss5RZMg6(_vu9fg466p$9{1fzSgFzkF72`YS_#- zT`8nAdIm1Fe)4eq!LxRLnRzR5Wy%g%*0U8`YQ zWa;NFC7=F0&+HYiZqTeA3kBRLR~cQz@M`mtPt)s5G<25hk}RES^yMTU7YKrvyV0I7 zL?v#@Nn)TVKVt;4AWKj?e^f0-M?O-O>6nSGB}uf)rLbS<@=$<|=ZuSPo<~;q=?p{3 zBS(r&cvS68eOdb83xpgCH3R)riL}-OnI;deF3i@#Tt<(j>tKh^EaBV*<&|!~9mhlbDX0xc(?G>~*D4Be>q(8s5&8oG~It?rfTu<%TH;WX7g3W z`bw{pbGR>B-VG8T_o!OHvfCDz!PRR%nfU~TXEh458ik7BL;qCU*Ak$U-uc1LJ&xp^ zpZG>4&U~nh8l*f%?9*2-JiHOFQ?HRtQ4ka=!vgsuS>`D==9UWMoSqu4fsfFdN`tpT z!yQY9Ts39V3{BrD^GV{9;g$f5A=k3*j74IB(WgDxS6Qgah*f3>b%QGFpC!WZB zRDqd>?)q`>5_cu64@C`{^0=ZODe}J9t?N6?qvp1vnvroLsloTUs?lYnM=VD$<*}dc zV5=svpb|A7q1OJ*WsUQV1(DL5>ilG_=#FDZ6ofpx5&aLw+|WIIL(H(ba%(S)aGe`OW^%z?cDgR*$)=f+N; z#?pg-?_czO$pU!H0!l8Q27FjS@8$CVMTA=Lf$x`PfF&^+gc4+cVFV4@Q=z5|4VKF? zfE5G;V5mS7zY-(?vMvptT9E-3^=U9{g#-3$?mq;=OI?=vaa4!EjliHw{9yMA&GQ*q z5d@ASLCT62V1WcrtxElqA?Urz1N2-2iPi#K#uw-SF38^ARg91y*@7S)kq5wAf-JT| zlK-6FbE|&MKZO3cHH01VO~=v(3~>?!gNd}e#}EYpa8Gj#>>(n6kKZ9|O%brLrD|os z@HGV8Y0#D^1PY^=z(+33;N%(t_~b;R$dlxNau*uh@n8Z$5(4mdqltJD=U!iIlBf^o z3BLaV2i3l?zSrLF2Q#rEWjZFkVE~C-6>)F62 zs;E`@Uy%tEq{st8H9ti(MFCi;`zda>Gl5GK1Q6axQ)zA~1LDmzn6bsZ_eL0O3j*qG xU?ZLvL~paeew*3-Qa7*fhRxfm0KA*ZInHBzfq^;)+ # Memory: collection-sources The five dimensions `collect` runs against a target, and exactly what to pull from each. Operational β€” no rationale. diff --git a/dist/tidyfactor-doc/references/memory/doc-templates.md b/dist/tidyfactor-doc/references/memory/doc-templates.md index 23289dc..8bb810d 100644 --- a/dist/tidyfactor-doc/references/memory/doc-templates.md +++ b/dist/tidyfactor-doc/references/memory/doc-templates.md @@ -1,3 +1,4 @@ + # Memory: doc-templates Shapes for each doc type `generate` produces. Templates, not prose β€” fill in from `collect` findings. diff --git a/dist/tidyfactor-doc/references/memory/doc-tree.md b/dist/tidyfactor-doc/references/memory/doc-tree.md index 0381e95..c26987c 100644 --- a/dist/tidyfactor-doc/references/memory/doc-tree.md +++ b/dist/tidyfactor-doc/references/memory/doc-tree.md @@ -1,3 +1,4 @@ + # Memory: doc-tree Canonical `/docs` folder shape and the manifest schema. Every command reads this before touching `/docs` so the structure stays identical across projects. diff --git a/dist/tidyfactor-doc/references/memory/docsify-config.md b/dist/tidyfactor-doc/references/memory/docsify-config.md index 278c7dc..e1dfcb7 100644 --- a/dist/tidyfactor-doc/references/memory/docsify-config.md +++ b/dist/tidyfactor-doc/references/memory/docsify-config.md @@ -1,3 +1,4 @@ + # Memory: docsify-config Fixed Docsify setup used by the `docsify` workflow. Not a menu of options β€” locked, production-tested config for TidyFactor documentation web portals. diff --git a/dist/tidyfactor-doc/references/memory/mkdocs-config.md b/dist/tidyfactor-doc/references/memory/mkdocs-config.md index c744e71..24f32ed 100644 --- a/dist/tidyfactor-doc/references/memory/mkdocs-config.md +++ b/dist/tidyfactor-doc/references/memory/mkdocs-config.md @@ -1,3 +1,4 @@ + # Memory: MkDocs Material Configuration Spec Complete architectural specification for scaffolding and compiling production-grade documentation portals with MkDocs Material. diff --git a/dist/tidyfactor-doc/references/memory/site-engines.md b/dist/tidyfactor-doc/references/memory/site-engines.md index 9658b29..96f24d0 100644 --- a/dist/tidyfactor-doc/references/memory/site-engines.md +++ b/dist/tidyfactor-doc/references/memory/site-engines.md @@ -1,3 +1,4 @@ + # Memory: Site Engines (Docsify vs. MkDocs Material) Technical evaluation matrix for documentation publishing engines supported by `tidyfactor-doc`. diff --git a/dist/tidyfactor-doc/references/workflows/collect.md b/dist/tidyfactor-doc/references/workflows/collect.md index 2632380..35f2b7e 100644 --- a/dist/tidyfactor-doc/references/workflows/collect.md +++ b/dist/tidyfactor-doc/references/workflows/collect.md @@ -6,6 +6,9 @@ One outcome: a structured findings file β€” `docs/.collected/.md` β€” th Run all five collection dimensions from `memory/collection-sources.md` against the target. Skip a dimension only if it genuinely doesn't apply (e.g., no Git history available for an uploaded snapshot) β€” note the skip and why, don't silently omit it. +0. **Active Context Discovery & Brain MCP (Fail-Open)**: + - Silently check if project architecture KIs exist via `search_knowledge_base(query="architecture routes apis", scope="project")` when Brain MCP is active. + - If Brain MCP is absent or returns empty, proceed with 0ms delay directly to step 1. 1. **Code parsing.** Extract existing docblocks/comments, function/method/class signatures, exported types, and public surface area directly from source. Flag anything already documented inline so `generate` doesn't duplicate it. 2. **Commit history.** Read `git log` and any available PR descriptions for the target's files. Pull out *why* behind non-obvious code β€” rationale, past bugs fixed, deliberate tradeoffs β€” not just *what* changed. 3. **Runtime & environment.** Enumerate required environment variables, config files, software dependencies (with version constraints), and any stated hardware/resource limits. **MANDATORY**: Scrub and redact any actual secrets, production server IPs, database passwords, or private API tokens found in `.env` or config filesβ€”record only variable names, expected formats, and generic placeholder values. @@ -14,6 +17,7 @@ Run all five collection dimensions from `memory/collection-sources.md` against t 6. **Write the findings** to `docs/.collected/.md` as plain structured notes under five headings matching the dimensions above β€” this is source material for `generate`, not a finished doc, so skip prose polish. 7. **Update `docs/.doc-manifest.json`**: add `` to the `collected` section with a timestamp. +8. **Optional Brain Sync (`--sync-brain`)**: Persist extracted architecture facts via `extract_knowledge_item` per `20-brain-baas-integration.md`. ## Validation checklist @@ -21,5 +25,6 @@ Run all five collection dimensions from `memory/collection-sources.md` against t - [ ] Every fact traces to something actually found in the code, history, config, or logs β€” nothing inferred or assumed - [ ] Zero sensitive data leaked: all real API keys, passwords, private IPs, and secrets are replaced with safe generic placeholders - [ ] No local workstation drive paths (`C:\...`, `file:///...`) exist in findings; all paths are normalized to project-relative paths +- [ ] Deterministic audit passed via `python scripts/audit_docs.py docs/.collected/.md` - [ ] `docs/.doc-manifest.json`'s `collected` section includes `` - [ ] Findings are organized by dimension, not pre-formatted as any particular doc type diff --git a/dist/tidyfactor-doc/tools/build-skill.js b/dist/tidyfactor-doc/tools/build-skill.js index 675dfd1..4ab0da9 100644 --- a/dist/tidyfactor-doc/tools/build-skill.js +++ b/dist/tidyfactor-doc/tools/build-skill.js @@ -23,6 +23,8 @@ const OUT_FILE = const ROOT_COPIES = [ "SKILL.md", "references", + "scripts", + "manifest.json", "tools", "bin", "brand.json", diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..5927af8 --- /dev/null +++ b/manifest.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://tidyfactor.com/schemas/skill-manifest.tools.schema.json", + "skill_id": "tidyfactor-doc", + "version": "1.4.0", + "skill_root_anchor": "self", + "tools": [ + { + "name": "audit_docs", + "description": "Audits documentation files under /docs for sensitive data leaks, banned absolute URLs, relative link integrity, and Docsify/MkDocs compliance.", + "runtime": "python", + "entrypoint": "scripts/audit_docs.py", + "args_schema": { + "type": "object", + "properties": { + "target": { + "type": "string", + "description": "Directory or markdown file path to audit (defaults to docs)" + }, + "json": { + "type": "boolean", + "description": "Output structured JSON report" + } + } + }, + "cli_pattern": "python {entrypoint} {target} --json", + "isolation_recommended": false + } + ] +} diff --git a/package.json b/package.json index d787156..301d119 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tidyfactor/doc", - "version": "1.3.0", + "version": "1.4.0", "description": "TidyFactor Doc β€” Code Documentation Builder & Dual-Engine Publishing Platform (MkDocs Material & Docsify) for AI Coding Agents", "main": "SKILL.md", "bin": { diff --git a/references/memory/20-brain-baas-integration.md b/references/memory/20-brain-baas-integration.md new file mode 100644 index 0000000..540f96b --- /dev/null +++ b/references/memory/20-brain-baas-integration.md @@ -0,0 +1,83 @@ +# Brain BaaS & Sovereign Knowledge Base Integration + + + +> **Tier**: Core Intelligence Layer Contract +> **Authority**: Sovereign Self-Hosted & Local Multi-Tenant Architecture +> **Protocol**: Model B (Fail-Open Sovereign Agent Protocol) + +--- + +## πŸ›οΈ Architectural Doctrine & Deployment Model + +TidyFactor is strictly the **Intelligence, Context & Operating Layer** for AI agents. `tidyfactor-doc` operates under **Model B (Sovereign Self-Hosted Architecture)**: + +1. **Zero Centralized Data Exposure**: Codebase documentation, API schemas, and technical architectures are never transmitted to a centralized multi-tenant cloud service. +2. **Local Multi-Tenant Isolation**: Each client or project maintains an isolated local SQLite knowledge base (`data/tenants/{tenant_id}_brain.sqlite`) or filesystem cache (`~/.gemini/knowledge/projects/{project_id}/`). +3. **Fail-Open Invariant**: Brain MCP integration is completely optional at runtime. If `tidyfactor-brain` MCP is unavailable or disabled, `tidyfactor-doc` executes directly via local filesystem AST parsing and markdown generation with **0ms latency penalty** and zero connection errors. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ AI Coding Agent Session β”‚ +β”‚ (Google Antigravity, Claude Code, Cursor) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + [1] Check Active Tool Manifest + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ + [Brain MCP Active] [Brain MCP Absent] + β”‚ β”‚ + [2] search_knowledge_base() β”‚ + (Doc Tree, APIs, Routes) β”‚ + β”‚ β”‚ + (Found?) β”‚ + β”œβ”€β”€ YES ──► Ingest KI Context β”‚ + └── NO ──► Direct Codebase Scan β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ (0ms Fail-Open) + β”‚ + [3] Execute Doc Workflow + (init / collect / generate / site) + β”‚ + [4] Persist /docs/ Artifacts + β”‚ + (Optional: --sync-brain) + β”‚ + [5] extract_knowledge_item() +``` + +--- + +## πŸ“‹ Documentation Knowledge Item (KI) Payload Schema + +When persisting documentation structures or API contracts to the Brain via `--sync-brain`, payload objects MUST conform to the standard KI schema: + +```json +{ + "title": "Documentation Architecture: [Project Name]", + "category": "technical_architecture", + "scope": "project", + "tags": ["documentation", "api-reference", "mkdocs", "docsify", "architecture"], + "content": "### Codebase Overview\n...", + "metadata": { + "skill": "tidyfactor-doc", + "version": "1.4.0", + "site_engine": "mkdocs|docsify|none", + "doc_root": "docs/", + "entry_point": "docs/index.md", + "sidebar_configured": true, + "api_endpoints_count": 14, + "stacks": ["php", "typescript", "python"], + "i18n_enabled": true + } +} +``` + +--- + +## ⚑ Fail-Open Context Resolution Flow + +Every documentation command (`init`, `collect`, `generate`, `site`) executes context resolution deterministically: + +1. **Local Filesystem Scan**: Inspect `docs/`, `mkdocs.yml`, `_sidebar.md`, and project manifest (`package.json`, `composer.json`, `pyproject.toml`). +2. **Optional Brain MCP Query**: If `search_knowledge_base` is active, retrieve existing architecture KIs for cross-referencing. +3. **Silent Bypass**: If no Brain tool exists, complete the documentation task directly without prompting the user about server status. diff --git a/references/memory/collection-sources.md b/references/memory/collection-sources.md index 8f72488..883ecbd 100644 --- a/references/memory/collection-sources.md +++ b/references/memory/collection-sources.md @@ -1,3 +1,4 @@ + # Memory: collection-sources The five dimensions `collect` runs against a target, and exactly what to pull from each. Operational β€” no rationale. diff --git a/references/memory/doc-templates.md b/references/memory/doc-templates.md index 23289dc..8bb810d 100644 --- a/references/memory/doc-templates.md +++ b/references/memory/doc-templates.md @@ -1,3 +1,4 @@ + # Memory: doc-templates Shapes for each doc type `generate` produces. Templates, not prose β€” fill in from `collect` findings. diff --git a/references/memory/doc-tree.md b/references/memory/doc-tree.md index 0381e95..c26987c 100644 --- a/references/memory/doc-tree.md +++ b/references/memory/doc-tree.md @@ -1,3 +1,4 @@ + # Memory: doc-tree Canonical `/docs` folder shape and the manifest schema. Every command reads this before touching `/docs` so the structure stays identical across projects. diff --git a/references/memory/docsify-config.md b/references/memory/docsify-config.md index 278c7dc..e1dfcb7 100644 --- a/references/memory/docsify-config.md +++ b/references/memory/docsify-config.md @@ -1,3 +1,4 @@ + # Memory: docsify-config Fixed Docsify setup used by the `docsify` workflow. Not a menu of options β€” locked, production-tested config for TidyFactor documentation web portals. diff --git a/references/memory/mkdocs-config.md b/references/memory/mkdocs-config.md index c744e71..24f32ed 100644 --- a/references/memory/mkdocs-config.md +++ b/references/memory/mkdocs-config.md @@ -1,3 +1,4 @@ + # Memory: MkDocs Material Configuration Spec Complete architectural specification for scaffolding and compiling production-grade documentation portals with MkDocs Material. diff --git a/references/memory/site-engines.md b/references/memory/site-engines.md index 9658b29..96f24d0 100644 --- a/references/memory/site-engines.md +++ b/references/memory/site-engines.md @@ -1,3 +1,4 @@ + # Memory: Site Engines (Docsify vs. MkDocs Material) Technical evaluation matrix for documentation publishing engines supported by `tidyfactor-doc`. diff --git a/references/workflows/collect.md b/references/workflows/collect.md index 2632380..35f2b7e 100644 --- a/references/workflows/collect.md +++ b/references/workflows/collect.md @@ -6,6 +6,9 @@ One outcome: a structured findings file β€” `docs/.collected/.md` β€” th Run all five collection dimensions from `memory/collection-sources.md` against the target. Skip a dimension only if it genuinely doesn't apply (e.g., no Git history available for an uploaded snapshot) β€” note the skip and why, don't silently omit it. +0. **Active Context Discovery & Brain MCP (Fail-Open)**: + - Silently check if project architecture KIs exist via `search_knowledge_base(query="architecture routes apis", scope="project")` when Brain MCP is active. + - If Brain MCP is absent or returns empty, proceed with 0ms delay directly to step 1. 1. **Code parsing.** Extract existing docblocks/comments, function/method/class signatures, exported types, and public surface area directly from source. Flag anything already documented inline so `generate` doesn't duplicate it. 2. **Commit history.** Read `git log` and any available PR descriptions for the target's files. Pull out *why* behind non-obvious code β€” rationale, past bugs fixed, deliberate tradeoffs β€” not just *what* changed. 3. **Runtime & environment.** Enumerate required environment variables, config files, software dependencies (with version constraints), and any stated hardware/resource limits. **MANDATORY**: Scrub and redact any actual secrets, production server IPs, database passwords, or private API tokens found in `.env` or config filesβ€”record only variable names, expected formats, and generic placeholder values. @@ -14,6 +17,7 @@ Run all five collection dimensions from `memory/collection-sources.md` against t 6. **Write the findings** to `docs/.collected/.md` as plain structured notes under five headings matching the dimensions above β€” this is source material for `generate`, not a finished doc, so skip prose polish. 7. **Update `docs/.doc-manifest.json`**: add `` to the `collected` section with a timestamp. +8. **Optional Brain Sync (`--sync-brain`)**: Persist extracted architecture facts via `extract_knowledge_item` per `20-brain-baas-integration.md`. ## Validation checklist @@ -21,5 +25,6 @@ Run all five collection dimensions from `memory/collection-sources.md` against t - [ ] Every fact traces to something actually found in the code, history, config, or logs β€” nothing inferred or assumed - [ ] Zero sensitive data leaked: all real API keys, passwords, private IPs, and secrets are replaced with safe generic placeholders - [ ] No local workstation drive paths (`C:\...`, `file:///...`) exist in findings; all paths are normalized to project-relative paths +- [ ] Deterministic audit passed via `python scripts/audit_docs.py docs/.collected/.md` - [ ] `docs/.doc-manifest.json`'s `collected` section includes `` - [ ] Findings are organized by dimension, not pre-formatted as any particular doc type diff --git a/scripts/audit_docs.py b/scripts/audit_docs.py new file mode 100644 index 0000000..6a933bf --- /dev/null +++ b/scripts/audit_docs.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +""" +TidyFactor Documentation Quality & Hygiene Auditor +Deterministic AST and pattern scanner for documentation files under /docs and root README. +Audits for sensitive data leaks, banned absolute URLs, relative link integrity, +and Docsify / MkDocs structural requirements. +""" + +import sys +import os +import re +import json +import argparse +from pathlib import Path + +# Ensure UTF-8 output on Windows console +if sys.stdout and hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="replace") +if sys.stderr and hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8", errors="replace") + +SENSITIVE_PATTERNS = [ + (r"(?i)(password|passwd|pwd)\s*[:=]\s*['\"][^'\"]+['\"]", "Hardcoded plain password"), + (r"(?i)(secret_key|secret|api_key|apikey|token)\s*[:=]\s*['\"][a-zA-Z0-9_\-\.]{12,}['\"]", "Hardcoded secret / API token"), + (r"(?i)cpanel_[a-zA-Z0-9_]+_pass", "cPanel credential pattern"), + (r"(?i)whm_[a-zA-Z0-9_]+_token", "WHM access token pattern"), + (r"ghp_[a-zA-Z0-9]{20,}", "GitHub Personal Access Token"), + (r"xox[baprs]-[0-9a-zA-Z]{10,}", "Slack Token"), + (r"AKIA[0-9A-Z]{16}", "AWS Access Key"), +] + +BANNED_URL_PATTERNS = [ + (r"file:///[a-zA-Z]:[/\\]", "Banned machine-specific absolute file URL"), + (r"file://c:/", "Banned Windows drive path URL"), + (r"[a-zA-Z]:\\wamp64\\www\\", "Banned internal workstation local path"), +] + +def audit_doc_file(file_path: Path) -> list: + issues = [] + try: + content = file_path.read_text(encoding="utf-8", errors="replace") + except Exception as e: + return [{"file": str(file_path), "severity": "high", "type": "read_error", "message": str(e)}] + + lines = content.splitlines() + + for idx, line in enumerate(lines, start=1): + # 1. Check for sensitive data leaks + for pattern, desc in SENSITIVE_PATTERNS: + if re.search(pattern, line): + # Ignore placeholders like YOUR_API_KEY, REDACTED, example + if not re.search(r"(?i)(your_|example|redacted|\.\.\.|placeholder|<.+>|dummy)", line): + issues.append({ + "file": str(file_path), + "line": idx, + "severity": "critical", + "type": "sensitive_data_leak", + "message": f"Potential sensitive data leak detected: {desc}." + }) + + # 2. Check for banned absolute workstation paths & file:/// URLs + for pattern, desc in BANNED_URL_PATTERNS: + if re.search(pattern, line): + issues.append({ + "file": str(file_path), + "line": idx, + "severity": "high", + "type": "banned_absolute_path", + "message": f"Banned workstation-specific path detected: {desc}. Use clean relative links or public URLs." + }) + + return issues + +def audit_docs_directory(target_dir: Path) -> dict: + all_issues = [] + files_scanned = 0 + + if target_dir.is_file(): + files_to_check = [target_dir] + elif target_dir.is_dir(): + files_to_check = list(target_dir.rglob("*.md")) + else: + return {"error": f"Target path does not exist: {target_dir}", "passed": False} + + for f in files_to_check: + # Skip vendor/node_modules/.git + if any(part in f.parts for part in [".git", "node_modules", "dist", "vendor"]): + continue + files_scanned += 1 + issues = audit_doc_file(f) + all_issues.extend(issues) + + critical_count = len([i for i in all_issues if i.get("severity") == "critical"]) + high_count = len([i for i in all_issues if i.get("severity") == "high"]) + + score = max(0, 100 - (critical_count * 30) - (high_count * 15)) + passed = critical_count == 0 and high_count == 0 + + return { + "target": str(target_dir), + "files_scanned": files_scanned, + "score": score, + "passed": passed, + "critical_issues": critical_count, + "high_issues": high_count, + "total_issues": len(all_issues), + "issues": all_issues, + "timestamp": "2026-09-02T06:00:00Z" + } + +def main(): + parser = argparse.ArgumentParser(description="TidyFactor Documentation Quality & Hygiene Auditor") + parser.add_argument("target", nargs="?", default="docs", help="Directory or markdown file to audit (default: docs)") + parser.add_argument("--json", action="store_true", help="Output pure JSON format") + + args = parser.parse_args() + target_path = Path(args.target) + + result = audit_docs_directory(target_path) + + if args.json: + print(json.dumps(result, indent=2, ensure_ascii=False)) + else: + status_str = "[PASS]" if result.get("passed") else "[FAIL]" + print(f"\n{status_str} Documentation Quality & Hygiene Audit β€” Score: {result.get('score', 0)}/100") + print(f"Target: {result.get('target')} | Files Scanned: {result.get('files_scanned', 0)}") + print(f"Issues Found: {result.get('total_issues', 0)} (Critical: {result.get('critical_issues', 0)}, High: {result.get('high_issues', 0)})\n") + + for iss in result.get("issues", []): + print(f" - [{iss.get('severity', '').upper()}] {iss.get('file')}:{iss.get('line', '?')} β€” {iss.get('message')}") + print() + + sys.exit(0 if result.get("passed") else 1) + +if __name__ == "__main__": + main() diff --git a/tools/build-skill.js b/tools/build-skill.js index 675dfd1..4ab0da9 100644 --- a/tools/build-skill.js +++ b/tools/build-skill.js @@ -23,6 +23,8 @@ const OUT_FILE = const ROOT_COPIES = [ "SKILL.md", "references", + "scripts", + "manifest.json", "tools", "bin", "brand.json",