Skip to content

設定 ​

導入完成後,blueprint.config.mjs 就是專案的架構權威。你可以匯出經 defineBlueprint() 驗證的物件,或使用內建預設設定:

js
import { defineBlueprint } from '@kekkai/blueprint';

export default defineBlueprint({
  name: 'storefront',
  framework: 'react',
  architecture: {
    alias: '~app',
    layers: [
      { name: 'pages', does: '組合路由頁面。' },
      { name: 'components', does: '介面呈現,包含功能私有元件。' },
      { name: 'services', does: '網路存取。', owns: ['axios'] },
    ],
  },
});

defineBlueprint() 會立刻驗證;Blueprint 載入一般物件時也會再次驗證。未知的結構欄位與 不成立的相依圖會回報明確錯誤,不會默默忽略。API 參考保留完整型別簽章; 本頁則說明這些欄位如何組成同一個模型。

套件提供兩個具有完整宣告檔的公開入口。架構編寫、預設設定、emitters 與 runtimes 使用 @kekkai/blueprint;需要組合 Blueprint 管理的執行期或產生文字時,整合工具可從 @kekkai/blueprint/operational-contract 使用具型別的 OperationalText renderers。一般導入 不需要使用此子路徑;確有整合需求時應匯入這個公開入口,不要直接引用 dist/ 內部檔案。

根欄位 ​

  • name
    • 型別與預設值:選填 string。
    • 用途:顯示於架構手冊與 Agent 守則的專案名稱。
  • framework
    • 型別與預設值:必填 'vue' | 'react' | 'auto'。
    • 用途:決定原始碼 glob 與框架限定規則;auto 交由執行期偵測。
  • architecture
    • 型別與預設值:必填 ArchitectureDef。
    • 用途:定義原始碼根目錄、拓樸、依賴方向、單元配置、別名與所有權。
  • rules
    • 型別與預設值:Record<string, RuleSetting>,預設 {}。
    • 用途:選用的 lint/執行期關卡與文件判斷準則。規則有備用數值,不代表未宣告時會自動啟用。
  • principles
    • 型別與預設值:PrincipleDef[],預設 []。
    • 用途:產生給人與 Agent 閱讀的核心工程信念。
  • componentShape
    • 型別與預設值:AxisDef[],預設 []。
    • 用途:各自獨立的元件設計判斷軸線。
  • playbook
    • 型別與預設值:PlaybookSection[],預設 []。
    • 用途:依主題分組的工作規則。
  • emit
    • 型別與預設值:選填 EmitDef。
    • 用途:架構手冊、Agent 守則與結構性 lint 的產出政策。

架構 ​

別名與原始碼根目錄 ​

js
architecture: {
  alias: '~app',
  additionalAliases: {
    '@legacy': './src',
    '@shared': './src/shared',
  },
  sourceRoot: 'src',
  // ...
}
  • alias 必填且沒有預設值,是跨越受管理模組或分層邊界時唯一標準的原始碼根別名。
  • additionalAliases 將既有別名對應至專案相對路徑,供解析與相依診斷使用;它們不會 變成跨邊界時的另一套標準寫法。 若在此重複標準 alias,只有正規化後仍指向相同原始碼根目錄才會接受;指向不同位置時, 會在設定驗證階段直接拒絕,不讓互相矛盾的別名身分進入 lint 產出、檢查或遷移建議。
  • sourceRoot 預設為 src。Next.js 未使用 src/ 等根目錄配置可設為 .。Lint、 inspect、deps、產生後的守則與建置路徑都以它為基準。

Layer-first ​

沒有 architecture.modules 時,實體結構是 Layer → Unit:

js
architecture: {
  alias: '~app',
  layers: [
    { name: 'pages', does: '路由與頁面組合。' },
    { name: 'components', does: '介面呈現,包含功能私有元件。' },
    { name: 'services', does: '網路存取原語。' },
  ],
}

分層宣告順序就是預設的單向流:前面的層可以匯入後面的層,反向則不行。以上例來說, pages 可匯入 components 與 services,services 則不能匯入上游。

Module-first ​

architecture.modules 以「有沒有宣告」決定拓樸:

  • 省略 — layer-first。
  • [] — module-first 起點:拓樸已宣告,但還沒有任何領域模組。
    所有內部分層、規則與理念都已生效;lint、inspect、deps、doctor、impact、
    架構手冊與 Agent 守則都會把這個應用程式當成 module-first。
    空的起點是刻意的狀態,不是導入沒做完。
  • 一個以上的模組 — 已建立領域模組的 module-first。

宣告模組後,一般模組的實體結構改為 Module → Layer → Unit:

js
architecture: {
  alias: '~app',
  modules: [
    { name: 'checkout', does: '結帳流程。', dependsOn: ['catalog'] },
    { name: 'catalog', does: '商品探索。' },
    { name: 'app', does: '路由組合。' },
  ],
  layers: [
    { name: 'components', does: '模組介面。' },
    { name: 'hooks', does: '狀態轉接。' },
    { name: 'services', does: '資料存取。' },
  ],
  layerFiles: '{module}/{layer}/**/*.{ts,tsx}',
}

每個一般模組都重複使用同一份分層定義,模組根目錄的檔案則佔 container 的位置:
它們負責組合自己與可達模組的內部分層,所以 layer-first 的 containers 層不會在模組裡重複出現。
dependsOn 列出直接依賴;實際權限依相依圖的 遞移可達性決定,與宣告順序無關。一次受管理的匯入必須同時通過外層模組圖與內部分層流。

模組名稱即使只有大小寫不同也不能重複。相依目標必須已宣告,且不得為空、重複、指向 自己或形成循環。

app 是選用的保留名稱。它代表 container 位置上的遞迴路由組合,不是一般領域模組, 也不重複內部分層。

原始碼中的直接模組相依 ​

Module-first 專案的 deps 與 inspect 會分別呈現三種相依圖:

相依圖意義
D:宣告的直接相依modules[].dependsOn 寫下的每條相依邊;即使另有間接路徑,也保留直接宣告。
P:匯入權限從 D 推導出的所有可達模組;模組匯入權限仍由它決定。
O:觀察到的直接相依依目前受管原始碼與標準模組身分,確認的跨模組直接參照。

例如宣告 a -> b -> c 時,a 匯入 c 仍有間接權限,但精確同步比對會指出 a.dependsOn 缺少 c。若 a 沒有直接參照 b,只有在 a 的分析完整時,才能將該宣告 列為未使用。觀察到的相依不會授予權限;流向、別名、相對路徑跨界與深入匯入違規仍獨立回報。

觀察範圍包含靜態匯入、副作用匯入、型別匯入與再匯出,以及可靜態確認目標的 require() 和動態匯入。來源須位於 sourceRoot,使用既有掃描器支援的 JS/TS/JSX/TSX/模組/ CommonJS/Vue 格式;套件相依、建置輸出、涵蓋率與快取目錄仍依既有規則排除。 符合 testFiles 的檔案不納入相依或不確定性;testFiles: [] 則取消豁免。 layerFilesIgnore 只排除 lint 與以 lint 為依據的檢查,不會隱藏模組相依。 產生的原始碼只要仍在受管範圍內,也照常計算;檔名、檔頭或註解都不是豁免依據。 同模組參照、沒有模組身分的來源根目錄組裝檔、外部套件及無法分類的目標不會形成跨模組相依。

執行時才能決定的匯入、解析失敗,以及無法解析的專案內參照,只會讓匯入者所屬模組的分析 不完整。已確認的相依仍保留;該模組未觀察到的宣告標為 unverified,不能當成 unused。 已可靠解析到來源目錄外的目標不算未知模組。外部使用端沒有模組身分;再廣的 consumerFiles 模式也不會改變受管檔案的身分。模組分析與外部範圍證據的完整性分開判定,因此模組結果可以是 synchronized,而外部證據仍不完整或無法取得。這是上述範圍內的原始碼觀察結果,並非完整的 執行時相依圖。

狀態意義
synchronizedD = O,且模組分析完整。
drift已確認缺少或未使用的宣告;即使同時有不確定性,也維持此狀態。
indeterminate尚無已確認的差異,但不確定性使精確比對無法完成。
runwayModule-first 宣告了 modules: []。

每個模組也有 reach:至少分析一個受管檔案時為 covered,否則為 vacuous。 空原始碼樹上的集合相等,不代表架構已有實際涵蓋範圍,也不證明領域所有權合理。 Layer-first 的 moduleDependencies 為 null,文字報告不顯示模組區段。

要啟用精確同步,先檢視 blueprint deps 或 blueprint inspect,判斷應修正原始碼還是宣告、 處理不確定性,再明確設定所需層級:

js
rules: {
  moduleDependencySync: 'error',
}

moduleDependencySync 接受 error、warn 或 off,沒有數值或自訂選項。 它由 Inspect 執行,不是 ESLint 規則,也不受 emit.lint.severity 影響。 未設定或設為 off 時仍可查看報告,但不產生同步檢查結果;warn 產生警告,error 則依 Inspect 原有規則影響結束狀態。Layer-first 會標示此關卡不可用;空模組起步狀態不產生差異問題。 既有專案與內建預設不會自動啟用,不需要生命週期遷移或改寫設定。Blueprint 不會自動新增、 移除、排序或依間接路徑刪減 dependsOn。觀察到禁止的相依,也不代表應直接補上權限。

一般模組的根單元 ​

一般模組的根目錄只要有直接受管理的原始碼檔案,就形成一個以模組目錄為位址的結構單元。 它必須有且只有一個支援的 index.* 入口;其他直接成員都是私有檔案。外部呼叫端使用模組 別名本身(例如 ~app/checkout),不得加上 /index 或私有檔名。私有成員必須能從入口 透過匯入或重新匯出的鏈結到達。Inspect 會以 orphan-root-file 回報已證明無法到達的成員, 也包含與入口斷開的循環;解析失敗與執行期才能決定的匯入仍列為分析限制。 入口缺失或不唯一時,應先處理入口問題,不能據此斷言其他成員都是孤立檔案。

已宣告內部分層的檔案仍屬於各自的單元。只有內部分層的模組不需要根入口或空的彙整檔, 任意子目錄也不會因此合法化。同一根單元內可使用相對匯入;跨界仍須遵守標準別名與分層 入口規則。內部分層不能向上匯入模組根單元。保留的遞迴 app 樹不套用根入口、私有路徑 或孤立成員檢查。相依圖維持模組/container 身分,不會把私有檔案變成公開架構單元。

舊專案跨越 4.2 升級時,必須人工完成 review-module-root-unit。已符合契約的根單元可保留 原位;Blueprint 不會代選匯出或產生應用程式 API。layer-first 專案只需確認不必遷移模組根單元。 只有未修改的標準 React/Vue 預設架構,才建議將呈現責任放在 components。所有自訂架構 都必須由負責人決定位置,即使分層名為 components,或只有 views.does 看似明確描述 呈現責任,也不能自動選定。執行工具不會解讀責任文字、增加分層或修改既有責任。 檔案私有或只有一個使用者,都不足以證明它負責流程協調。

LF → MF 時,應在保留的轉換來源與逐檔決策中記錄原路徑、單元身分、分層定義與自訂入口名。 多個 LF 單元合併成一個 MF 根單元,也不會抹除原身分。MF → LF 時,有效來源紀錄應逐檔還原 至原單元,保留 Screen 等原入口名稱;MF 的固定 index 規則不能覆寫原 LF 入口。 只有完全沒有保留 LF 來源時,才可提議 containers/<module> 作為備選,並檢查負責人決策、 路徑衝突與目標入口。來源不完整、矛盾或無法對應時,必須明列待決事項,不得改用備選、 遺漏成員或重建已不存在的單元。MF 新增而無來源的成員也須明確決定歸屬。 驗證必須涵蓋目的地唯一性與完整性、大小寫衝突、匯出、改寫後的參照,以及 LF 的入口與相依規則。 歷史身分還原與呈現責任歸屬是兩項不同的審查;衝突時交由負責人決定,不得靜默覆寫來源紀錄。 只驗證路徑的工具無法證明任意 API 改動仍等價。

分層欄位 ​

  • name
    • 型別與預設值:必填非空 string。
    • 意義:資料夾/分層識別碼。不得重複,也不能是路徑或保留的產出檔名。
  • does
    • 型別與預設值:必填 string,不可為空字串或只含空白。
    • 意義:一行責任說明,會寫入產出守則。
  • mustNot
    • 型別與預設值:(string | ExecutableMustNot)[],預設 []。
    • 意義:依宣告順序保留人工審查責任與選擇啟用的可執行限制。見可執行的 mustNot。
  • layout
    • 型別與預設值:'folder' | 'file',預設 'file'。
    • 意義:Folder layout 的單元具有公開入口;file layout 採分層粒度的相依關係。
  • entry
    • 型別與預設值:string,預設 'index'。
    • 意義:Folder layout 單元的公開入口檔名。
  • allowedImporters
    • 型別與預設值:選填 (string | AllowedImporter)[]。
    • 意義:縮小哪些上游層可以匯入本層;省略時允許所有較早宣告的層。
  • owns
    • 型別與預設值:OwnedPrimitive[],預設 []。
    • 意義:只允許本層使用的套件、具名匯入或全域物件。
  • lintOverrides
    • 型別與預設值:Record<string, unknown>,預設 {}。
    • 意義:本層的 ESLint 覆寫;Blueprint 管理的限制規則不能在這裡取代。

物件形式的 importer 可加入 selfOnly 與 description:

js
{
  name: 'contexts',
  does: '定義 Context 與 Provider。',
  allowedImporters: [
    { layer: 'containers', description: '只掛載 Provider' },
    { layer: 'hooks', selfOnly: true, description: '只使用 Context' },
  ],
}

selfOnly 允許依賴,但禁止繼續再匯出。每個 importer 必須是不重複、且已在更前面宣告的 分層,因此這個欄位只能縮小單向圖,不能建立回頭依賴。

所有權有三種寫法:

js
owns: [
  'axios',
  { package: 'react', imports: ['useContext'] },
  { package: '@company/*', pattern: true, exempt: ['**/*.adapter.ts'] },
  { global: 'WebSocket' },
]
  • 字串代表擁有完整套件/模組識別字。
  • { package, imports } 只擁有具名匯入;pattern 將套件名稱視為 glob 群組,exempt 則排除符合條件的檔案。
  • { global } 代表不經 import 宣告的全域物件。

檔案範圍與命名 ​

  • layerFiles
    • 預設值:依框架產生原始碼 glob。
    • 意義:可填一個或多個 glob。Layer-first 必須包含 {layer};module-first 必須同時包含 {module} 與 {layer}。
  • layerFilesIgnore
    • 預設值:無。
    • 意義:從產生的 lint 與 lint 型 finding 排除;其他 inspect 分析仍可能看見。
  • testFiles
    • 預設值:**/*.test.{js,jsx,ts,tsx,vue} 與 **/*.spec.{js,jsx,ts,tsx,vue}。
    • 意義:從結構/度量分析與相依圖排除,同時作為測試限定規則範圍。[] 會關閉兩者,也讓 testFilename 沒有作用範圍。
  • naming
    • 預設值:{}。
    • 意義:依概念命名的文字慣例,會寫入架構手冊與 Agent 守則。

可攜式 glob 使用 /、**、*、? 與 *.{ts,tsx} 這類單層大括號選項。Lint 與 inspect 共用的語法不包含反向條件、字元集合、extglob 或巢狀大括號。

外部使用端 ​

architecture.consumerFiles?: string | string[] 用來明確宣告哪些原始碼檔案會使用受管應用程式, 但不屬於模組 → 分層 → 單元拓樸。預設不宣告任何外部使用端;省略欄位或填入 [] 都維持既有 行為。單一字串等同只有一筆模式的陣列;非字串、空字串或只有空白的模式都會讓設定驗證失敗。

js
consumerFiles: ['.storybook/preview.ts', 'e2e/**/*.{ts,tsx}', 'scripts/seed.ts'],

模式使用與 testFiles 相同的可攜式比對器,路徑以 Blueprint 應用程式根目錄為基準, 不是 sourceRoot 或程序的暫存目錄。支援 /、**、*、? 與單層大括號選項;不支援 否定、字元類別、延伸 glob 或巢狀大括號。調換模式順序或重複等效模式,都不影響分類結果。

只有已掃描且位於 sourceRoot 外的檔案能取得這個角色。即使 **/*.stories.tsx 同時命中 受管原始碼,也不會豁免根目錄內的檔案。sourceRoot: '.' 沒有根目錄外的原始碼集合,因此 宣告不會產生效果。Layer-first 的行為不變。預設設定、框架與工具名稱都不會自動宣告外部 使用端;模式應盡量精確,過寬的宣告可能掩蓋真正遺漏在架構外的應用程式原始碼。

Module-first 的範圍分析允許已宣告的外部使用端匯入受管原始碼,不因此回報 uncovered-application-source;受管原始碼反向匯入外部使用端,仍然是錯誤。分類先按檔案 判定,再彙整資料夾,因此同一資料夾裡未宣告的匯入者仍會被回報。外部使用端沒有模組、分層、 單元、容器、所有權或公開入口身分,也不會增加 dependsOn、單元相依圖或架構涵蓋範圍的證據。 這只是原始碼範圍的區分,不代表已驗證標準別名、深入匯入、目標 API 或公開入口的使用是否正確。

testFiles 保留獨立政策,並且優先處理:符合的測試檔會先從兩個方向排除,再進行使用端分類。 layerFilesIgnore 不能隱藏範圍證據;掃描排除規則、additionalAliases 與基準線也維持原有用途。 宣告不會搬移或建立檔案、擴大掃描範圍,或壓掉其他適用的問題。產生的手冊與 Agent 指引只記錄 宣告模式及其契約,不嵌入會隨原始碼變動而過期的命中檔案清單。是否啟用由專案負責人決定, 既有設定不需要遷移。

規則 ​

規則可直接填 tier,或使用含 tier 與選項的物件:

js
rules: {
  maxLines: { tier: 'error', value: 400 },
  usePrefix: { tier: 'error', layer: 'hooks', prefix: 'use' },
  codeStyle: { tier: 'warn', indent: 2, quotes: 'single', semi: true, maxLen: 90 },
  deadCode: 'warn',
}

有效 tier 為 error、warn、off。未宣告的選用規則不會產生。度量規則只有在已宣告但 省略 value 時,才會採用備用數值。

  • maxLines — 由 max-lines 執行;備用值為 400,只計算程式行。
  • maxLinesPerFunction — 由 max-lines-per-function 執行;備用值為 100,只計算程式行。
  • maxParams — 由 max-params 執行;備用值為 3。
  • maxStatements — 由 max-statements 執行;備用值為 15。
  • complexity — 由 complexity 執行;備用值為 12。
  • unusedVars — 由 core 或 TypeScript 版 no-unused-vars 執行;底線開頭的參數可忽略,變數改成底線開頭不算刪除。
  • explicitAny — 由 @typescript-eslint/no-explicit-any 執行;只有提供 TypeScript 外掛時產生。
  • codeStyle — 由 @stylistic 規則組與 curly 執行;預設縮排 2、單引號、分號、最大行長 90。
  • statementsPerLine — 由 @stylistic/max-statements-per-line 執行;固定最多 1 個 statement。
  • statementPadding — 由 @stylistic/padding-line-between-statements 執行;採固定留白政策。
  • importBlock — 由 import-x/first 與 import-x/no-duplicates 執行;需要 import-x 外掛。
  • fixtureImports — 由結構性 restricted imports 執行;禁止正式程式碼從別名下的 fixtures 路徑匯入。
  • deepWatch — 由 blueprint/no-deep-watch 執行;只適用 Vue。
  • usePrefix — 由 blueprint/use-prefix 執行;預設分層為 hooks、前綴為 use。
  • usePrefixReactivity — 由 blueprint/use-prefix-needs-reactivity 執行;檢查 use 命名單元是否在同一檔案直接呼叫可辨識的響應式/生命週期 API。它不會追查被呼叫的其他 hooks,因此警告不能證明函式是純函式;重新命名或搬移前,應先檢查組合的 hooks。
  • testFilename — 由 blueprint/test-filename-matches-source 執行;使用 architecture.testFiles,空陣列時無法啟用。
  • typedefOnlyFile — 由 blueprint/no-typedef-only-file 執行;只適用 JavaScript 檔案。
  • moduleDependencySync — 由 Inspect 執行的 module-first 選用關卡,精確比對直接相依;詳見原始碼中的直接模組相依。
  • cycles — 由 inspect 的循環 finding 執行;執行指令或 CI 時分析,預設不是 ESLint 規則。
  • deadCode 與其他識別碼 — 只寫入產出守則,沒有機器關卡;可另用 knip 等合適工具。

結構性規則不屬於這份選用清單。只要執行 emitLint,模組可達性、分層流、標準別名、單元 入口、相對路徑逃逸、所有權與 selfOnly 就會由 architecture 產生。

請用 blueprint rules 查看真實專案的有效規則。若自行呼叫 emitLint,第三方規則需要在 EmitLintOptions 提供對應的 typescript、stylistic 或 imports 外掛;自動產生的設定檔 已經完成接線。

核心信念、元件設計與工作指南 ​

這三組欄位承載專案自己的工程理念,不假裝所有判斷都能自動化:

js
principles: [{
  id: 'single-source',
  say: '維持單一真相來源。',
  why: '衍生值不該成為重複的可變狀態。',
  land: 'claude',
}],
componentShape: [{
  id: 'narrow-inputs',
  name: '收窄輸入',
  say: '單元只依賴真正需要的內容。',
  why: '介面越小,耦合越少。',
  triage: 'max-params',
}],
playbook: [{
  title: '審查',
  rules: [{ id: 'measure-first', say: '最佳化前先量測。' }],
}],
  • principle 需要不重複的 id、say、why 與 land(lint 或 claude)。
  • component axis 需要不重複的 id、name、say 與 why;triage 可指定用來找出候選 問題的規則。
  • 每個執行指南區段都要有非空 title;規則 id 必須在所有區段間保持唯一,並 提供 say 與選填 why。

Vue 與 React 預設設定會帶入 Blueprint 的標準理念;自訂設定可保留、替換或省略。概念本身 見工程理念。

產出目標 ​

js
emit: {
  handbook: 'docs/architecture-handbook.md',
  agents: [
    'agents',
    { target: 'cursor', path: '.cursor/rules/architecture.mdc' },
  ],
  lint: { severity: 'warn' },
}
  • emit.handbook
    • 預設值:docs/architecture-handbook.md。
    • 意義:相對於專案根目錄的架構手冊路徑。
  • emit.agents
    • 預設值:['claude', 'agents']。
    • 意義:守則目標。可用值為 claude、agents、gemini、copilot、cursor、windsurf;物件形式可改路徑,[] 代表完全不產生。
  • emit.lint.severity
    • 預設值:'error'。
    • 意義:只控制結構性規則;選用 rules 仍使用各自 tier。

Agent 目標不能重複,自訂路徑也不能是空字串。預設路徑、合併/完整管理策略與生命週期見 產出檔案。

預設設定 ​

js
import { nextPreset, reactPreset, vuePreset } from '@kekkai/blueprint';

export default vuePreset({ name: 'admin', alias: '~app' });
// export default reactPreset({ name: 'web', emit: { agents: ['agents'] } });
// export default nextPreset({ router: 'app', srcDir: true });

vuePreset() 與 reactPreset() 提供標準單向應用程式分層、所有權、規則、核心信念、元件 軸線與工作指南。選項為 name、alias(預設 ~app)、emit 與 modules。

modules 跟 architecture.modules 用同一條「有沒有宣告」規則。
省略時,預設設定維持 layer-first 分層。
給 [] 或宣告模組時,預設設定會投影成 module-first:pages 與 containers 離開內部分層 ——
路由組合交給保留的 app 模組,模組根目錄佔 container 的位置 ——
其餘分層守則、規則、核心信念、元件軸線與工作指南全部不變:

js
// 以 --topology module-first 導入、經量測確認沒有原始碼的應用程式:
export default reactPreset({ name: 'web', modules: [] });

nextPreset() 使用 React 語意,並依 app、pages 或 both 調整 layer-first 路由樹; router 預設為 app。srcDir: true 會選擇 src,否則原始碼根目錄為 .;別名預設為 @。由於 Server Component 原本就可能在路由樹各處取得資料,它不會將 fetch 限定在單一層。

從 3.2 升級到 4.0 ​

有效的 3.2 設定仍是既有 layer-first 專案的權威。執行 4.0 init 會先完成支援的正規化與 修復。若同一次執行要求 module-first,Blueprint 會先把正規化後的 layer-first 設定寫成可 復原的檢查點,並停在此處。接著再次執行 init --topology module-first,才會從明確的 4.0 權威啟動受保護的轉換;不會在同一步裡重新解讀舊格式並搬移原始碼。

可執行的 mustNot ​

同一層可以混用既有文字與明確的可執行檢查。字串一律是 review only,由人與 Agent 審查;Blueprint 不會從文字、description、層名或檔名推導規則。React、Vue、Next 預設設定 中的字串維持原意,舊設定不需要改寫生命週期狀態。

js
mustNot: [
  'change product policy',
  {
    id: 'render-ui',
    description: 'render UI',
    checks: [
      { capability: 'render-ui' },
      { package: 'leaflet' },
      { package: 'react-leaflet' },
      { package: 'react-leaflet-cluster' },
      { call: 'divIcon' },
    ],
    exempt: ['src/domain/approved-adapter.ts'],
  },
]

同一項目的檢查採 OR 語意:任一檢查命中,就違反這項限制。id 必須符合 [a-z][a-z0-9-]*,且在該層中唯一;description、判別欄位的值與 checks 不可為空。 每個檢查只能使用 capability、package、global、call 其中一個判別欄位;未知欄位或 能力名稱會直接拒絕。正規化後重複的檢查,以及空白或重複的 imports 名稱也會拒絕。 exempt 使用可攜的專案相對 glob,在架構檔案範圍確定後套用到該項目的所有檢查。 輸出保留項目與檢查的宣告順序。

明確指定 Vue 的設定不能宣告 React 專屬能力,反之亦然。framework: 'auto' 則延後到 專案解析時確認相容性;無法確認必要的框架或 parser 支援時,回報 unavailable,不捏造 原始碼違規。直接呼叫 emitLint 的整合可以用 detectedFramework 提供 auto 的解析結果。

專案自訂檢查 ​

檢查精確範圍與限制
{ package: 'leaflet' }比對套件本身與 / 子路徑,不比對 leaflet-extra。@org/pkg 也不比對 @org/pkg-extra。這是套件家族比對,與 owns 的精確路徑語意不同。涵蓋靜態與 type-only 匯入/再匯出、可證明來源的 require(),以及能靜態化簡的動態匯入;執行期才確定的來源維持未驗證。
{ package: 'leaflet/*', pattern: true }使用既有 ownership/no-restricted-imports 群組比對語意,不額外展開套件家族。
{ package: 'react', imports: ['useState'] }比對靜態匯入與再匯出的具名匯出身分,包含別名與 type-only 形式。不推論 CommonJS/動態匯入的 namespace 成員、後續屬性存取、重新賦值或包裝函式;需要涵蓋這些情況時,選用完整套件、能力或 call 檢查。
{ global: 'WebSocket' }比對未被區域繫結遮蔽的值參照與支援的型別參照。宣告、屬性鍵、標籤、字面值與同名區域繫結合法。parser 無法提供必要參照空間時,覆蓋能力為 unavailable。
{ call: 'divIcon' }比對直接呼叫與末端成員名稱,包含 L.divIcon()、L['divIcon']()、可選呼叫,以及 JS/TS 與 SFC script。這是語法名稱比對,無關物件的同名方法也可能命中。不追蹤解構別名、包裝函式、重新賦值、執行期計算的名稱、建構子或 Vue template 呼叫。

能力偵測表 ​

框架呼叫必須有可證明的匯入繫結,包含別名與 namespace 成員;無關的同名區域函式不會 命中。React 類別必須繼承可證明來自 React 的 Component 或 PureComponent。

能力訊號
render-uiJSX 元素/fragment;含可渲染元素、元件、插值或非空白文字的 Vue template;React createElement;Vue h/createVNode;React 類別的 render 方法。
react-stateReact useState、useReducer、useSyncExternalStore;React 類別擁有 state 或使用 setState。
react-lifecycleReact useEffect、useLayoutEffect、useInsertionEffect,以及下列類別生命週期宣告。
vue-reactivityVue ref、shallowRef、reactive、shallowReactive、customRef、computed、toRef、toRefs。
vue-lifecycleVue onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted、onActivated、onDeactivated、onErrorCaptured、onRenderTracked、onRenderTriggered、onServerPrefetch。
routing可證明指向 react-router、react-router-dom、vue-router、next/router、next/navigation、next/link 的套件參照,包含子路徑與 type-only 參照。
network-io未被遮蔽的直接 fetch、XMLHttpRequest、WebSocket、EventSource 與 navigator.sendBeacon。
browser-dom未被遮蔽的直接 window、document、navigator、HTMLElement、Element、Node、EventTarget、CustomEvent、MutationObserver、ResizeObserver、IntersectionObserver、localStorage、sessionStorage、location、history、customElements、getComputedStyle。

React 類別生命週期宣告包含 componentDidMount、componentDidUpdate、componentWillUnmount、 shouldComponentUpdate、getSnapshotBeforeUpdate、getDerivedStateFromProps、 getDerivedStateFromError、componentDidCatch、componentWillMount、componentWillReceiveProps、 componentWillUpdate、UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps、 UNSAFE_componentWillUpdate。一般類別即使使用同名方法,仍是合法對照。

這些偵測器不證明透過包裝函式、相依注入、應用程式抽象、執行期行為或跨檔案值流造成的 間接行為。通過只表示實際檢查範圍內沒有觀察到已列明的訊號。

可用性、嚴重程度、串接與涵蓋範圍 ​

Lint、Rules、Inspect、Doctor 與產出守則共用一份解析結果。字串為 review-only,沒有 機器載體。可執行項目在所有檢查可用時為 active,只有部分可用時為 partial,全部 不可用時為 unavailable;partial 項目中可用的檢查仍持續執行。可用性與 lint 串接狀態 (verified、unverified、reference-only)及檔案涵蓋狀態(covered、vacuous、 runway、unmeasured)彼此獨立。沒有原始碼的具體位置是 vacuous,modules: [] 是未來 模組的 runway,兩者都不代表已受行為保護。保留的 app 使用容器角色,不重複內層。

證據關卡證據退出與完成判定
emit.lint.severity: 'error' 的已證實命中failedaffectsExit: true;執行 lint 回傳非零,阻擋完成。
同一命中改為 warn可見但不阻擋的符合性觀察affectsExit: false;不改變彙總關卡,也不單獨造成非零退出。
必要偵測器 partial/unavailableunverified,不捏造違規不自動改變 CLI 退出碼;不論嚴重程度都阻擋完成。
已證實載體缺失、停用、替換或不相符failed沿用 Doctor 失敗行為;即使是 warn 也阻擋完成。
無法檢查實際生效設定unverified阻擋完成,但不自動造成非零退出。
檢查可用、載體完整、沒有 error 命中passed可同時有警告;仍須滿足其他完成條件。

混合情況保留 failed 與 unverified 各自的證據,彙總時 failed 優先。只有 lint 偵測原始碼 違規;Inspect 測量涵蓋範圍,不建立第二套 AST 偵測器,靜態 Rules 也不宣稱現場命中。 Doctor 用代表性或合成檔案驗證最後合併的 ESLint 設定,包括空位置。受管理的 blueprint/must-not 載體不能透過 lintOverrides 停用或替換。

先套用結構規則的 sourceRoot、層/模組 glob、框架副檔名、layerFilesIgnore、testFiles, 再套用項目豁免。本功能不新增 Inspect 退出碼政策。