qfai 0.6.0 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +206 -22
- package/dist/cli/index.cjs +125 -25
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +125 -25
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +2 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.mjs +2 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,46 @@
|
|
|
1
1
|
# QFAI Toolkit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/qfai)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
|
|
7
|
+
品質重視型AI駆動運用モデル(SDD × ATDD × TDD)を単一パッケージで提供するツールキットです。
|
|
8
|
+
|
|
9
|
+
## 目次
|
|
10
|
+
|
|
11
|
+
- [インストール](#インストール)
|
|
12
|
+
- [Quick Start](#quick-start最短成功)
|
|
13
|
+
- [機能](#できること)
|
|
14
|
+
- [CLI リファレンス](#使い方cli)
|
|
15
|
+
- [設定](#設定)
|
|
16
|
+
- [契約](#契約contracts)
|
|
17
|
+
- [Monorepo 対応](#monorepo--サブディレクトリ)
|
|
18
|
+
- [CI 統合](#ci-と-hard-gate)
|
|
19
|
+
- [GitHub Actions](#github-actions-テンプレート)
|
|
20
|
+
- [開発](#開発)
|
|
21
|
+
- [ライセンス](#ライセンス)
|
|
22
|
+
|
|
23
|
+
## インストール
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
npm install qfai
|
|
27
|
+
```
|
|
4
28
|
|
|
5
|
-
|
|
29
|
+
または
|
|
6
30
|
|
|
7
|
-
```
|
|
8
|
-
|
|
31
|
+
```sh
|
|
32
|
+
npx qfai init
|
|
9
33
|
```
|
|
10
34
|
|
|
11
|
-
|
|
35
|
+
**必要環境**: Node.js >= 18
|
|
12
36
|
|
|
13
|
-
|
|
37
|
+
## パッケージ
|
|
38
|
+
|
|
39
|
+
- `qfai`: CLI + コア + テンプレートを同梱
|
|
40
|
+
|
|
41
|
+
## Quick Start(最短成功)
|
|
42
|
+
|
|
43
|
+
```sh
|
|
14
44
|
npx qfai init
|
|
15
45
|
npx qfai validate --fail-on error --format github
|
|
16
46
|
npx qfai report
|
|
@@ -24,33 +54,76 @@ npx qfai report
|
|
|
24
54
|
- `npx qfai doctor` による設定/探索/パス/glob/validate.json の事前診断
|
|
25
55
|
- `npx qfai report` によるレポート出力
|
|
26
56
|
|
|
27
|
-
|
|
57
|
+
補足: v0.x は日本語テンプレ中心で提供します。将来は英語を正本、日本語を別ドキュメントに切り替える方針です。
|
|
58
|
+
|
|
59
|
+
## 使い方(CLI)
|
|
60
|
+
|
|
61
|
+
`validate` は `--fail-on` / `--strict` によって CI ゲート化できます。`validate` は常に `.qfai/out/validate.json`(`output.validateJsonPath`)へ JSON を出力します。`--format` は画面表示(text/github)のみを制御します。`--format github` はアノテーションの上限と重複排除を行い、先頭にサマリを出します(全量は `validate.json` か `--format text` を参照)。
|
|
62
|
+
`report` は `.qfai/out/validate.json` を既定入力とし、`--in` で上書きできます(優先順位: CLI > config)。`--run-validate` を指定すると validate を実行してから report を生成します。出力先は `--out` で変更できます(`--format json` の場合は `.qfai/out/report.json`)。
|
|
63
|
+
`doctor` は validate/report の前段で設定/探索/パス/glob/validate.json を診断します。`--format text|json`、`--out` をサポートし、診断のみ(修復はしません)。`--fail-on warning|error` を指定すると該当 severity 以上で exit 1(未指定は常に exit 0)になります。
|
|
64
|
+
`report.json` は非契約(experimental / internal)として扱います。外部 consumer は依存しないでください。フィールドは例であり固定ではありません。短い例:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"tool": "qfai",
|
|
69
|
+
"summary": {
|
|
70
|
+
"specs": 1,
|
|
71
|
+
"scenarios": 1,
|
|
72
|
+
"contracts": { "api": 0, "ui": 1, "db": 0 },
|
|
73
|
+
"counts": { "info": 0, "warning": 0, "error": 0 }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
doctor(text)の例:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
qfai doctor: root=. config=qfai.config.yaml (found)
|
|
82
|
+
[ok] config.search: qfai.config.yaml found
|
|
83
|
+
summary: ok=10 warning=2 error=0
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
doctor の JSON も非契約(内部形式。将来予告なく変更あり)です。フィールドは例であり固定ではありません。短い例:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"tool": "qfai",
|
|
91
|
+
"checks": [
|
|
92
|
+
{
|
|
93
|
+
"id": "config.search",
|
|
94
|
+
"severity": "ok",
|
|
95
|
+
"message": "qfai.config.yaml found"
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
28
100
|
|
|
29
|
-
`validate` は `--fail-on` / `--strict` によって CI ゲート化できます。`validate` は常に `.qfai/out/validate.json`(`output.validateJsonPath`)へ JSON を出力し、`--format` は表示形式(text/github)のみを制御します。
|
|
30
|
-
`report` は `.qfai/out/validate.json` を読み、既定で `.qfai/out/report.md` を出力します(`--format json` の場合は `.qfai/out/report.json`)。出力先は `--out` で変更できます。入力パスは固定です。
|
|
31
|
-
`doctor` は validate/report の前段で設定/探索/パス/glob/validate.json を診断します。`--format text|json`、`--out` をサポートします。
|
|
32
101
|
`init --yes` は予約フラグです(現行の init は非対話のため挙動差はありません)。既存ファイルがある場合は `--force` が必要です。
|
|
33
|
-
|
|
102
|
+
|
|
103
|
+
## 設定
|
|
34
104
|
|
|
35
105
|
設定はリポジトリ直下の `qfai.config.yaml` で行います。
|
|
36
106
|
命名規約は `docs/rules/naming.md` を参照してください。
|
|
37
107
|
|
|
38
|
-
## Contracts
|
|
108
|
+
## 契約(Contracts)
|
|
39
109
|
|
|
40
|
-
Spec では `QFAI-CONTRACT-REF:` 行で参照する契約IDを宣言します(`none`
|
|
41
|
-
Scenario では `# QFAI-CONTRACT-REF:` のコメント行で契約参照を宣言します(`none`
|
|
110
|
+
Spec では `QFAI-CONTRACT-REF:` 行で参照する契約IDを宣言します(`none` 可)。Spec の先頭 H1 に `SPEC-xxxx` が必須です。
|
|
111
|
+
Scenario では `# QFAI-CONTRACT-REF:` のコメント行で契約参照を宣言します(`none` 可)。
|
|
42
112
|
契約ファイルは `QFAI-CONTRACT-ID: <ID>` を **1ファイル1ID** で宣言します。
|
|
43
|
-
|
|
44
|
-
宣言では大文字(`UI-0001`)、ファイル名は小文字(`ui-0001-...`)を使用します。
|
|
113
|
+
`validate.json` / `report` の file path は root 相対で出力します(absolute は出力しません)。
|
|
45
114
|
|
|
46
|
-
|
|
115
|
+
## Monorepo / サブディレクトリ
|
|
47
116
|
|
|
48
|
-
- `
|
|
49
|
-
- `
|
|
50
|
-
-
|
|
51
|
-
- `unknownContractIdSeverity` は Scenario 側(`QFAI-TRACE-008`)のみを制御
|
|
117
|
+
- `--root` 未指定時は cwd から親へ `qfai.config.yaml` を探索します(見つからない場合は defaultConfig + warning)。
|
|
118
|
+
- monorepo ではパッケージ単位に `qfai.config.yaml` を置くか、`--root` で明示します。
|
|
119
|
+
- `paths.outDir` はパッケージごとに分け、`out/` の衝突を避けてください。
|
|
52
120
|
|
|
53
|
-
|
|
121
|
+
例(pnpm workspace):
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
packages/<app-a>/qfai.config.yaml # paths.outDir: .qfai/out/<app-a>
|
|
125
|
+
packages/<app-b>/qfai.config.yaml # paths.outDir: .qfai/out/<app-b>
|
|
126
|
+
```
|
|
54
127
|
|
|
55
128
|
## CI と Hard Gate
|
|
56
129
|
|
|
@@ -68,3 +141,114 @@ SC→Test 検証は `validation.traceability.scMustHaveTest` と
|
|
|
68
141
|
- `validation.traceability.testFileExcludeGlobs`: 追加の除外 glob(配列、任意)
|
|
69
142
|
- `validation.traceability.scMustHaveTest`: SC→Test 検証の有効/無効を制御(`true` で有効、`false` で無効)
|
|
70
143
|
- `validation.traceability.scNoTestSeverity`: SC 未参照時の重要度を指定(`error` / `warning`)
|
|
144
|
+
|
|
145
|
+
## GitHub Actions テンプレート
|
|
146
|
+
|
|
147
|
+
`npx qfai init` で `.github/workflows/qfai.yml` を生成します。テンプレートは `validate` ジョブで `.qfai/out/validate.json` を生成し、`qfai-validation` として artifact をアップロードします。`report` はテンプレートには含まれないため、必要なら別ジョブまたはローカルで `qfai report` を実行してください。
|
|
148
|
+
|
|
149
|
+
テンプレートは npm 前提です。pnpm を使う場合は `cache` と install コマンドを置き換えてください。
|
|
150
|
+
各 Actions のバージョンは運用方針に合わせて指定してください。
|
|
151
|
+
|
|
152
|
+
追加で `report` を回す場合の最小例:
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
jobs:
|
|
156
|
+
report:
|
|
157
|
+
needs: validate
|
|
158
|
+
runs-on: ubuntu-latest
|
|
159
|
+
steps:
|
|
160
|
+
- uses: actions/checkout@v4
|
|
161
|
+
- uses: actions/setup-node@v4
|
|
162
|
+
with:
|
|
163
|
+
node-version: lts/*
|
|
164
|
+
cache: npm
|
|
165
|
+
- run: npm ci
|
|
166
|
+
- uses: actions/download-artifact@v4
|
|
167
|
+
with:
|
|
168
|
+
name: qfai-validation
|
|
169
|
+
path: .qfai/out
|
|
170
|
+
- run: npx qfai report --out .qfai/out/report.md
|
|
171
|
+
- uses: actions/upload-artifact@v4
|
|
172
|
+
with:
|
|
173
|
+
name: qfai-report
|
|
174
|
+
path: .qfai/out/report.md
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
validate.json のスキーマと例は `docs/schema` / `docs/examples` を参照してください。
|
|
178
|
+
PromptPack は非契約(互換保証なし)です。編集する場合はラップ運用を推奨します。
|
|
179
|
+
|
|
180
|
+
## 生成される構成(例)
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
qfai.config.yaml
|
|
184
|
+
.qfai/
|
|
185
|
+
README.md
|
|
186
|
+
require/
|
|
187
|
+
README.md
|
|
188
|
+
specs/
|
|
189
|
+
README.md
|
|
190
|
+
spec-0001/
|
|
191
|
+
spec.md
|
|
192
|
+
delta.md
|
|
193
|
+
scenario.md
|
|
194
|
+
rules/
|
|
195
|
+
conventions.md
|
|
196
|
+
pnpm.md
|
|
197
|
+
promptpack/
|
|
198
|
+
constitution.md
|
|
199
|
+
steering/
|
|
200
|
+
compatibility-vs-change.md
|
|
201
|
+
traceability.md
|
|
202
|
+
naming.md
|
|
203
|
+
commands/
|
|
204
|
+
plan.md
|
|
205
|
+
implement.md
|
|
206
|
+
review.md
|
|
207
|
+
release.md
|
|
208
|
+
roles/
|
|
209
|
+
qa.md
|
|
210
|
+
spec.md
|
|
211
|
+
test.md
|
|
212
|
+
modes/
|
|
213
|
+
compatibility.md
|
|
214
|
+
change.md
|
|
215
|
+
prompts/
|
|
216
|
+
README.md
|
|
217
|
+
makeOverview.md
|
|
218
|
+
makeBusinessFlow.md
|
|
219
|
+
require-to-spec.md
|
|
220
|
+
qfai-generate-test-globs.md
|
|
221
|
+
qfai-maintain-traceability.md
|
|
222
|
+
qfai-maintain-contracts.md
|
|
223
|
+
qfai-classify-change.md
|
|
224
|
+
contracts/
|
|
225
|
+
README.md
|
|
226
|
+
api/
|
|
227
|
+
api-0001-sample.yaml
|
|
228
|
+
ui/
|
|
229
|
+
ui-0001-sample.yaml
|
|
230
|
+
db/
|
|
231
|
+
db-0001-sample.sql
|
|
232
|
+
out/
|
|
233
|
+
README.md
|
|
234
|
+
tests/
|
|
235
|
+
qfai-traceability.sample.test.ts
|
|
236
|
+
.github/
|
|
237
|
+
workflows/
|
|
238
|
+
qfai.yml
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## 開発
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
pnpm install
|
|
245
|
+
pnpm build
|
|
246
|
+
pnpm format:check
|
|
247
|
+
pnpm lint
|
|
248
|
+
pnpm check-types
|
|
249
|
+
pnpm test:assets
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## ライセンス
|
|
253
|
+
|
|
254
|
+
[MIT](https://github.com/aganesy/QFAI/blob/main/LICENSE)
|
package/dist/cli/index.cjs
CHANGED
|
@@ -889,8 +889,8 @@ var import_promises6 = require("fs/promises");
|
|
|
889
889
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
890
890
|
var import_node_url = require("url");
|
|
891
891
|
async function resolveToolVersion() {
|
|
892
|
-
if ("0.6.
|
|
893
|
-
return "0.6.
|
|
892
|
+
if ("0.6.3".length > 0) {
|
|
893
|
+
return "0.6.3";
|
|
894
894
|
}
|
|
895
895
|
try {
|
|
896
896
|
const packagePath = resolvePackageJsonPath();
|
|
@@ -1021,6 +1021,22 @@ async function createDoctorData(options) {
|
|
|
1021
1021
|
message: validateJsonExists ? "validate.json exists (report can run)" : "validate.json is missing (run 'qfai validate' before 'qfai report')",
|
|
1022
1022
|
details: { path: toRelativePath(root, validateJsonAbs) }
|
|
1023
1023
|
});
|
|
1024
|
+
const outDirAbs = resolvePath(root, config, "outDir");
|
|
1025
|
+
const rel = import_node_path7.default.relative(outDirAbs, validateJsonAbs);
|
|
1026
|
+
const inside = rel !== "" && !rel.startsWith("..") && !import_node_path7.default.isAbsolute(rel);
|
|
1027
|
+
addCheck(checks, {
|
|
1028
|
+
id: "output.pathAlignment",
|
|
1029
|
+
severity: inside ? "ok" : "warning",
|
|
1030
|
+
title: "Output path alignment",
|
|
1031
|
+
message: inside ? "validateJsonPath is under outDir" : "validateJsonPath is not under outDir (may be intended, but check configuration)",
|
|
1032
|
+
details: {
|
|
1033
|
+
outDir: toRelativePath(root, outDirAbs),
|
|
1034
|
+
validateJsonPath: toRelativePath(root, validateJsonAbs)
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
if (options.rootExplicit) {
|
|
1038
|
+
addCheck(checks, await buildOutDirCollisionCheck(root));
|
|
1039
|
+
}
|
|
1024
1040
|
const scenarioFiles = await collectScenarioFiles(specsRoot);
|
|
1025
1041
|
const globs = normalizeGlobs2(config.validation.traceability.testFileGlobs);
|
|
1026
1042
|
const exclude = normalizeGlobs2([
|
|
@@ -1052,23 +1068,9 @@ async function createDoctorData(options) {
|
|
|
1052
1068
|
details: { globs, excludeGlobs: exclude, error: String(error2) }
|
|
1053
1069
|
});
|
|
1054
1070
|
}
|
|
1055
|
-
const outDirAbs = resolvePath(root, config, "outDir");
|
|
1056
|
-
const rel = import_node_path7.default.relative(outDirAbs, validateJsonAbs);
|
|
1057
|
-
const inside = rel !== "" && !rel.startsWith("..") && !import_node_path7.default.isAbsolute(rel);
|
|
1058
|
-
addCheck(checks, {
|
|
1059
|
-
id: "output.pathAlignment",
|
|
1060
|
-
severity: inside ? "ok" : "warning",
|
|
1061
|
-
title: "Output path alignment",
|
|
1062
|
-
message: inside ? "validateJsonPath is under outDir" : "validateJsonPath is not under outDir (may be intended, but check configuration)",
|
|
1063
|
-
details: {
|
|
1064
|
-
outDir: toRelativePath(root, outDirAbs),
|
|
1065
|
-
validateJsonPath: toRelativePath(root, validateJsonAbs)
|
|
1066
|
-
}
|
|
1067
|
-
});
|
|
1068
1071
|
return {
|
|
1069
1072
|
tool: "qfai",
|
|
1070
1073
|
version,
|
|
1071
|
-
doctorFormatVersion: 1,
|
|
1072
1074
|
generatedAt,
|
|
1073
1075
|
root: toRelativePath(process.cwd(), root),
|
|
1074
1076
|
config: {
|
|
@@ -1080,6 +1082,90 @@ async function createDoctorData(options) {
|
|
|
1080
1082
|
checks
|
|
1081
1083
|
};
|
|
1082
1084
|
}
|
|
1085
|
+
var DEFAULT_CONFIG_SEARCH_IGNORE_GLOBS = [
|
|
1086
|
+
...DEFAULT_TEST_FILE_EXCLUDE_GLOBS,
|
|
1087
|
+
"**/.pnpm/**",
|
|
1088
|
+
"**/tmp/**",
|
|
1089
|
+
"**/.mcp-tools/**"
|
|
1090
|
+
];
|
|
1091
|
+
async function buildOutDirCollisionCheck(root) {
|
|
1092
|
+
try {
|
|
1093
|
+
const result = await detectOutDirCollisions(root);
|
|
1094
|
+
const relativeRoot = toRelativePath(process.cwd(), result.monorepoRoot);
|
|
1095
|
+
const configRoots = result.configRoots.map((configRoot) => toRelativePath(result.monorepoRoot, configRoot)).sort((a, b) => a.localeCompare(b));
|
|
1096
|
+
const collisions = result.collisions.map((item) => ({
|
|
1097
|
+
outDir: toRelativePath(result.monorepoRoot, item.outDir),
|
|
1098
|
+
roots: item.roots.map(
|
|
1099
|
+
(collisionRoot) => toRelativePath(result.monorepoRoot, collisionRoot)
|
|
1100
|
+
).sort((a, b) => a.localeCompare(b))
|
|
1101
|
+
})).sort((a, b) => a.outDir.localeCompare(b.outDir));
|
|
1102
|
+
const severity = collisions.length > 0 ? "warning" : "ok";
|
|
1103
|
+
const message = collisions.length > 0 ? `outDir collision detected (count=${collisions.length})` : `outDir collision not detected (configs=${configRoots.length})`;
|
|
1104
|
+
return {
|
|
1105
|
+
id: "output.outDirCollision",
|
|
1106
|
+
severity,
|
|
1107
|
+
title: "OutDir collision",
|
|
1108
|
+
message,
|
|
1109
|
+
details: {
|
|
1110
|
+
monorepoRoot: relativeRoot,
|
|
1111
|
+
configRoots,
|
|
1112
|
+
collisions
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
} catch (error2) {
|
|
1116
|
+
return {
|
|
1117
|
+
id: "output.outDirCollision",
|
|
1118
|
+
severity: "error",
|
|
1119
|
+
title: "OutDir collision",
|
|
1120
|
+
message: "OutDir collision scan failed",
|
|
1121
|
+
details: { error: String(error2) }
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
async function detectOutDirCollisions(root) {
|
|
1126
|
+
const monorepoRoot = await findMonorepoRoot(root);
|
|
1127
|
+
const configPaths = await collectFilesByGlobs(monorepoRoot, {
|
|
1128
|
+
globs: ["**/qfai.config.yaml"],
|
|
1129
|
+
ignore: DEFAULT_CONFIG_SEARCH_IGNORE_GLOBS
|
|
1130
|
+
});
|
|
1131
|
+
const configRoots = Array.from(
|
|
1132
|
+
new Set(configPaths.map((configPath) => import_node_path7.default.dirname(configPath)))
|
|
1133
|
+
).sort((a, b) => a.localeCompare(b));
|
|
1134
|
+
const outDirToRoots = /* @__PURE__ */ new Map();
|
|
1135
|
+
for (const configRoot of configRoots) {
|
|
1136
|
+
const { config } = await loadConfig(configRoot);
|
|
1137
|
+
const outDir = import_node_path7.default.normalize(resolvePath(configRoot, config, "outDir"));
|
|
1138
|
+
const roots = outDirToRoots.get(outDir) ?? /* @__PURE__ */ new Set();
|
|
1139
|
+
roots.add(configRoot);
|
|
1140
|
+
outDirToRoots.set(outDir, roots);
|
|
1141
|
+
}
|
|
1142
|
+
const collisions = [];
|
|
1143
|
+
for (const [outDir, roots] of outDirToRoots.entries()) {
|
|
1144
|
+
if (roots.size > 1) {
|
|
1145
|
+
collisions.push({
|
|
1146
|
+
outDir,
|
|
1147
|
+
roots: Array.from(roots).sort((a, b) => a.localeCompare(b))
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return { monorepoRoot, configRoots, collisions };
|
|
1152
|
+
}
|
|
1153
|
+
async function findMonorepoRoot(startDir) {
|
|
1154
|
+
let current = import_node_path7.default.resolve(startDir);
|
|
1155
|
+
while (true) {
|
|
1156
|
+
const gitPath = import_node_path7.default.join(current, ".git");
|
|
1157
|
+
const workspacePath = import_node_path7.default.join(current, "pnpm-workspace.yaml");
|
|
1158
|
+
if (await exists4(gitPath) || await exists4(workspacePath)) {
|
|
1159
|
+
return current;
|
|
1160
|
+
}
|
|
1161
|
+
const parent = import_node_path7.default.dirname(current);
|
|
1162
|
+
if (parent === current) {
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
current = parent;
|
|
1166
|
+
}
|
|
1167
|
+
return import_node_path7.default.resolve(startDir);
|
|
1168
|
+
}
|
|
1083
1169
|
|
|
1084
1170
|
// src/cli/lib/logger.ts
|
|
1085
1171
|
function info(message) {
|
|
@@ -1118,15 +1204,26 @@ async function runDoctor(options) {
|
|
|
1118
1204
|
rootExplicit: options.rootExplicit
|
|
1119
1205
|
});
|
|
1120
1206
|
const output = options.format === "json" ? formatDoctorJson(data) : formatDoctorText(data);
|
|
1207
|
+
const exitCode = shouldFailDoctor(data.summary, options.failOn) ? 1 : 0;
|
|
1121
1208
|
if (options.outPath) {
|
|
1122
1209
|
const outAbs = import_node_path8.default.isAbsolute(options.outPath) ? options.outPath : import_node_path8.default.resolve(process.cwd(), options.outPath);
|
|
1123
1210
|
await (0, import_promises8.mkdir)(import_node_path8.default.dirname(outAbs), { recursive: true });
|
|
1124
1211
|
await (0, import_promises8.writeFile)(outAbs, `${output}
|
|
1125
1212
|
`, "utf-8");
|
|
1126
1213
|
info(`doctor: wrote ${outAbs}`);
|
|
1127
|
-
return;
|
|
1214
|
+
return exitCode;
|
|
1128
1215
|
}
|
|
1129
1216
|
info(output);
|
|
1217
|
+
return exitCode;
|
|
1218
|
+
}
|
|
1219
|
+
function shouldFailDoctor(summary, failOn) {
|
|
1220
|
+
if (!failOn) {
|
|
1221
|
+
return false;
|
|
1222
|
+
}
|
|
1223
|
+
if (failOn === "error") {
|
|
1224
|
+
return summary.error > 0;
|
|
1225
|
+
}
|
|
1226
|
+
return summary.warning + summary.error > 0;
|
|
1130
1227
|
}
|
|
1131
1228
|
|
|
1132
1229
|
// src/cli/commands/init.ts
|
|
@@ -3021,13 +3118,11 @@ async function createReportData(root, validation, configResult) {
|
|
|
3021
3118
|
normalizeScSources(resolvedRoot, scSources)
|
|
3022
3119
|
);
|
|
3023
3120
|
const version = await resolveToolVersion();
|
|
3024
|
-
const reportFormatVersion = 1;
|
|
3025
3121
|
const displayRoot = toRelativePath(resolvedRoot, resolvedRoot);
|
|
3026
3122
|
const displayConfigPath = toRelativePath(resolvedRoot, configPath);
|
|
3027
3123
|
return {
|
|
3028
3124
|
tool: "qfai",
|
|
3029
3125
|
version,
|
|
3030
|
-
reportFormatVersion,
|
|
3031
3126
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3032
3127
|
root: displayRoot,
|
|
3033
3128
|
configPath: displayConfigPath,
|
|
@@ -3877,12 +3972,16 @@ async function run(argv, cwd) {
|
|
|
3877
3972
|
}
|
|
3878
3973
|
return;
|
|
3879
3974
|
case "doctor":
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3975
|
+
{
|
|
3976
|
+
const exitCode = await runDoctor({
|
|
3977
|
+
root: options.root,
|
|
3978
|
+
rootExplicit: options.rootExplicit,
|
|
3979
|
+
format: options.doctorFormat,
|
|
3980
|
+
...options.doctorOut !== void 0 ? { outPath: options.doctorOut } : {},
|
|
3981
|
+
...options.failOn && options.failOn !== "never" ? { failOn: options.failOn } : {}
|
|
3982
|
+
});
|
|
3983
|
+
process.exitCode = exitCode;
|
|
3984
|
+
}
|
|
3886
3985
|
return;
|
|
3887
3986
|
default:
|
|
3888
3987
|
error(`Unknown command: ${command}`);
|
|
@@ -3910,6 +4009,7 @@ Options:
|
|
|
3910
4009
|
--format <text|json> doctor \u306E\u51FA\u529B\u5F62\u5F0F
|
|
3911
4010
|
--strict validate: warning \u4EE5\u4E0A\u3067 exit 1
|
|
3912
4011
|
--fail-on <error|warning|never> validate: \u5931\u6557\u6761\u4EF6
|
|
4012
|
+
--fail-on <error|warning> doctor: \u5931\u6557\u6761\u4EF6
|
|
3913
4013
|
--out <path> report/doctor: \u51FA\u529B\u5148
|
|
3914
4014
|
--in <path> report: validate.json \u306E\u5165\u529B\u5148\uFF08config\u3088\u308A\u512A\u5148\uFF09
|
|
3915
4015
|
--run-validate report: validate \u3092\u5B9F\u884C\u3057\u3066\u304B\u3089 report \u3092\u751F\u6210
|