@fenglimg/fabric-cli 2.0.0-rc.22 → 2.0.0-rc.25
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 +1 -2
- package/dist/{chunk-4HC5ZK7H.js → chunk-AXKII55Y.js} +6 -6
- package/dist/{chunk-ZSESMG6L.js → chunk-COI5VDFU.js} +0 -12
- package/dist/{chunk-KZ2YITOS.js → chunk-STLR2GHP.js} +137 -1
- package/dist/{config-AYP5F72E.js → config-XGUUAYX6.js} +1 -1
- package/dist/{doctor-HIX2FFEP.js → doctor-DXKPYPRC.js} +245 -14
- package/dist/index.js +10 -7
- package/dist/{install-WJZQZM7D.js → install-S2J76N2B.js} +106 -67
- package/dist/onboard-coverage-JJ5NGU7I.js +213 -0
- package/dist/{plan-context-hint-RYVSMULL.js → plan-context-hint-KPGOW3QC.js} +1 -1
- package/dist/{serve-6PPQX7AW.js → serve-NPCI342P.js} +1 -1
- package/dist/{uninstall-L2HEEOU3.js → uninstall-MQM6NUFM.js} +2 -2
- package/package.json +3 -3
- package/templates/hooks/archive-hint.cjs +463 -0
- package/templates/hooks/configs/claude-code.json +3 -3
- package/templates/hooks/configs/codex-hooks.json +3 -3
- package/templates/hooks/fabric-hint.cjs +153 -93
- package/templates/hooks/lib/cite-contract-reminder.cjs +173 -0
- package/templates/hooks/lib/cite-line-parser.cjs +118 -0
- package/templates/skills/fabric-archive/SKILL.md +696 -7
- package/templates/skills/fabric-import/SKILL.md +26 -4
- package/templates/skills/fabric-review/SKILL.md +1 -1
- package/dist/chunk-PSVKSMRO.js +0 -896
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
3. 在目标项目运行 `fabric install`,完成一站式安装。
|
|
10
10
|
4. 启动 `fabric serve`,再去客户端里验证 `fab_plan_context` 和 `fab_get_knowledge_sections`。
|
|
11
11
|
|
|
12
|
-
`fabric install` 会自动准备 bootstrap、MCP 配置和 git hooks。公共命令面只保留 `install`、`doctor`、`serve`、`uninstall`、`config`(rc.
|
|
12
|
+
`fabric install` 会自动准备 bootstrap、MCP 配置和 git hooks。公共命令面只保留 `install`、`doctor`、`serve`、`uninstall`、`config`(rc.23 起移除了 baseline scan 机制,知识库唯一合法来源是 Skill 路径:`fabric-archive` / `fabric-import` / `fabric-review`)。
|
|
13
13
|
|
|
14
14
|
## 常用命令
|
|
15
15
|
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
- `fabric doctor --json`
|
|
19
19
|
- `fabric doctor --strict`
|
|
20
20
|
- `fabric doctor --fix`
|
|
21
|
-
- `fabric doctor --rescan`(替代旧的 `fabric scan`)
|
|
22
21
|
- `fabric serve`
|
|
23
22
|
- `fabric uninstall`
|
|
24
23
|
- `fabric config`(rc.16 起将提供配置面板;当前为占位提示)
|
|
@@ -109,14 +109,14 @@ var HOOK_CONFIG_ARRAY_PATHS = {
|
|
|
109
109
|
};
|
|
110
110
|
var FABRIC_HOOK_COMMAND_PATHS = {
|
|
111
111
|
claudeCode: {
|
|
112
|
-
fabricHint: "
|
|
113
|
-
knowledgeHintBroad: "
|
|
114
|
-
knowledgeHintNarrow: "
|
|
112
|
+
fabricHint: "${CLAUDE_PROJECT_DIR}/.claude/hooks/fabric-hint.cjs",
|
|
113
|
+
knowledgeHintBroad: "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-broad.cjs",
|
|
114
|
+
knowledgeHintNarrow: "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-narrow.cjs"
|
|
115
115
|
},
|
|
116
116
|
codex: {
|
|
117
|
-
fabricHint: "
|
|
118
|
-
knowledgeHintBroad: "
|
|
119
|
-
knowledgeHintNarrow: "
|
|
117
|
+
fabricHint: '"$(git rev-parse --show-toplevel)/.codex/hooks/fabric-hint.cjs"',
|
|
118
|
+
knowledgeHintBroad: '"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-broad.cjs"',
|
|
119
|
+
knowledgeHintNarrow: '"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-narrow.cjs"'
|
|
120
120
|
},
|
|
121
121
|
cursor: {
|
|
122
122
|
fabricHint: ".cursor/hooks/fabric-hint.cjs",
|
|
@@ -3,17 +3,6 @@
|
|
|
3
3
|
// src/dev-mode.ts
|
|
4
4
|
import { existsSync, readFileSync } from "fs";
|
|
5
5
|
import { isAbsolute, join, resolve } from "path";
|
|
6
|
-
function readFabricConfig(workspaceRoot = process.cwd()) {
|
|
7
|
-
const configPath = join(workspaceRoot, "fabric.config.json");
|
|
8
|
-
if (!existsSync(configPath)) {
|
|
9
|
-
return {};
|
|
10
|
-
}
|
|
11
|
-
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
12
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
13
|
-
throw new Error(`Expected object in ${configPath}`);
|
|
14
|
-
}
|
|
15
|
-
return parsed;
|
|
16
|
-
}
|
|
17
6
|
function resolveDevMode(cliTarget, workspaceRoot = process.cwd()) {
|
|
18
7
|
const envTarget = normalizeTarget(process.env.EXTERNAL_FIXTURE_PATH, workspaceRoot);
|
|
19
8
|
const directTarget = normalizeTarget(cliTarget, workspaceRoot);
|
|
@@ -51,7 +40,6 @@ function formatResolutionStep(source, value) {
|
|
|
51
40
|
}
|
|
52
41
|
|
|
53
42
|
export {
|
|
54
|
-
readFabricConfig,
|
|
55
43
|
resolveDevMode,
|
|
56
44
|
createDebugLogger
|
|
57
45
|
};
|
|
@@ -12,7 +12,10 @@ import { readFile } from "fs/promises";
|
|
|
12
12
|
import { join, resolve } from "path";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
14
|
import { cancel, intro, isCancel, log, outro, select, text } from "@clack/prompts";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
getPanelFields,
|
|
17
|
+
ONBOARD_SLOT_NAMES
|
|
18
|
+
} from "@fenglimg/fabric-shared";
|
|
16
19
|
import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
17
20
|
import { defineCommand } from "citty";
|
|
18
21
|
async function loadFabricConfig(workspaceRoot) {
|
|
@@ -33,6 +36,131 @@ function resolveServerPath(override) {
|
|
|
33
36
|
}
|
|
34
37
|
var PANEL_CONFIG_RELATIVE_PATH = [".fabric", "fabric-config.json"];
|
|
35
38
|
var EXIT_CHOICE = "__exit__";
|
|
39
|
+
async function readOnboardSlotsList(configPath) {
|
|
40
|
+
const raw = await readFile(configPath, "utf8");
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
43
|
+
throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
|
|
44
|
+
}
|
|
45
|
+
const obj = parsed;
|
|
46
|
+
const list = obj.onboard_slots_opted_out;
|
|
47
|
+
const optedOut = Array.isArray(list) ? list.filter((v) => typeof v === "string") : [];
|
|
48
|
+
return { config: obj, optedOut };
|
|
49
|
+
}
|
|
50
|
+
function ensureUninitGate(workspaceRoot) {
|
|
51
|
+
const configPath = join(workspaceRoot, ...PANEL_CONFIG_RELATIVE_PATH);
|
|
52
|
+
const fabricDir = join(workspaceRoot, ".fabric");
|
|
53
|
+
const fabricDirOk = existsSync(fabricDir) && statSync(fabricDir).isDirectory();
|
|
54
|
+
const configOk = fabricDirOk && existsSync(configPath);
|
|
55
|
+
if (!configOk) {
|
|
56
|
+
console.error(t("cli.config.errors.uninit-workspace.message"));
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return configPath;
|
|
60
|
+
}
|
|
61
|
+
function validateSlotArg(slot) {
|
|
62
|
+
if (slot === void 0 || slot.length === 0) {
|
|
63
|
+
console.error(`Missing required <slot> argument. Valid slots: ${ONBOARD_SLOT_NAMES.join(", ")}.`);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (!ONBOARD_SLOT_NAMES.includes(slot)) {
|
|
67
|
+
console.error(`Unknown slot "${slot}". Valid slots: ${ONBOARD_SLOT_NAMES.join(", ")}.`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return slot;
|
|
71
|
+
}
|
|
72
|
+
var dismissSlotCmd = defineCommand({
|
|
73
|
+
meta: {
|
|
74
|
+
name: "dismiss-slot",
|
|
75
|
+
description: "Add an S5 onboard slot to the opted-out list (fabric-archive Skill onboard phase invokes this).",
|
|
76
|
+
hidden: true
|
|
77
|
+
},
|
|
78
|
+
args: {
|
|
79
|
+
slot: {
|
|
80
|
+
type: "positional",
|
|
81
|
+
description: "Slot name to dismiss (one of the locked S5 set).",
|
|
82
|
+
required: true
|
|
83
|
+
},
|
|
84
|
+
target: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "Override the project root (defaults to cwd)."
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
async run({ args }) {
|
|
90
|
+
const slot = validateSlotArg(args.slot);
|
|
91
|
+
if (slot === null) {
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const workspaceRoot = resolve(args.target ?? process.cwd());
|
|
96
|
+
const configPath = ensureUninitGate(workspaceRoot);
|
|
97
|
+
if (configPath === null) {
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const { config, optedOut } = await readOnboardSlotsList(configPath);
|
|
103
|
+
if (optedOut.includes(slot)) {
|
|
104
|
+
console.log(`Slot "${slot}" already opted out; no-op.`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const next = [...optedOut, slot];
|
|
108
|
+
const merged = { ...config, onboard_slots_opted_out: next };
|
|
109
|
+
await atomicWriteJson(configPath, merged);
|
|
110
|
+
console.log(`Dismissed onboard slot "${slot}". Run \`fab config onboard-reset ${slot}\` to re-open.`);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
113
|
+
console.error(`dismiss-slot failed: ${message}`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
var onboardResetCmd = defineCommand({
|
|
119
|
+
meta: {
|
|
120
|
+
name: "onboard-reset",
|
|
121
|
+
description: "Remove an S5 onboard slot from the opted-out list \u2014 re-opens the slot for future fabric-archive onboard prompts.",
|
|
122
|
+
hidden: true
|
|
123
|
+
},
|
|
124
|
+
args: {
|
|
125
|
+
slot: {
|
|
126
|
+
type: "positional",
|
|
127
|
+
description: "Slot name to reset (one of the locked S5 set).",
|
|
128
|
+
required: true
|
|
129
|
+
},
|
|
130
|
+
target: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "Override the project root (defaults to cwd)."
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
async run({ args }) {
|
|
136
|
+
const slot = validateSlotArg(args.slot);
|
|
137
|
+
if (slot === null) {
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const workspaceRoot = resolve(args.target ?? process.cwd());
|
|
142
|
+
const configPath = ensureUninitGate(workspaceRoot);
|
|
143
|
+
if (configPath === null) {
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const { config, optedOut } = await readOnboardSlotsList(configPath);
|
|
149
|
+
if (!optedOut.includes(slot)) {
|
|
150
|
+
console.log(`Slot "${slot}" not opted out; no-op.`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const next = optedOut.filter((s) => s !== slot);
|
|
154
|
+
const merged = { ...config, onboard_slots_opted_out: next };
|
|
155
|
+
await atomicWriteJson(configPath, merged);
|
|
156
|
+
console.log(`Reset onboard slot "${slot}"; it will appear in \`fab onboard-coverage\` as missing again.`);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
159
|
+
console.error(`onboard-reset failed: ${message}`);
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
36
164
|
var configCmd = defineCommand({
|
|
37
165
|
meta: {
|
|
38
166
|
name: "config",
|
|
@@ -45,7 +173,15 @@ var configCmd = defineCommand({
|
|
|
45
173
|
valueHint: "path"
|
|
46
174
|
}
|
|
47
175
|
},
|
|
176
|
+
subCommands: {
|
|
177
|
+
"dismiss-slot": dismissSlotCmd,
|
|
178
|
+
"onboard-reset": onboardResetCmd
|
|
179
|
+
},
|
|
48
180
|
async run({ args }) {
|
|
181
|
+
const argvSub = process.argv[3];
|
|
182
|
+
if (argvSub === "dismiss-slot" || argvSub === "onboard-reset") {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
49
185
|
const workspaceRoot = resolve(args.target ?? process.cwd());
|
|
50
186
|
const configPath = join(workspaceRoot, ...PANEL_CONFIG_RELATIVE_PATH);
|
|
51
187
|
const fabricDir = join(workspaceRoot, ".fabric");
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
runInitScan
|
|
4
|
-
} from "./chunk-PSVKSMRO.js";
|
|
5
2
|
import {
|
|
6
3
|
hasActionHint,
|
|
7
4
|
paint,
|
|
@@ -13,7 +10,7 @@ import {
|
|
|
13
10
|
} from "./chunk-6ICJICVU.js";
|
|
14
11
|
import {
|
|
15
12
|
resolveDevMode
|
|
16
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-COI5VDFU.js";
|
|
17
14
|
|
|
18
15
|
// src/commands/doctor.ts
|
|
19
16
|
import { confirm, isCancel } from "@clack/prompts";
|
|
@@ -21,7 +18,9 @@ import { defineCommand } from "citty";
|
|
|
21
18
|
import {
|
|
22
19
|
appendEventLedgerEvent,
|
|
23
20
|
checkLockOrThrow,
|
|
21
|
+
enrichDescriptions,
|
|
24
22
|
runDoctorApplyLint as runDoctorFixKnowledge,
|
|
23
|
+
runDoctorArchiveHistory,
|
|
25
24
|
runDoctorCiteCoverage,
|
|
26
25
|
runDoctorFix,
|
|
27
26
|
runDoctorReport
|
|
@@ -59,11 +58,6 @@ var doctorCommand = defineCommand({
|
|
|
59
58
|
description: t("cli.doctor.args.json.description"),
|
|
60
59
|
default: false
|
|
61
60
|
},
|
|
62
|
-
rescan: {
|
|
63
|
-
type: "boolean",
|
|
64
|
-
description: t("cli.doctor.args.rescan.description"),
|
|
65
|
-
default: false
|
|
66
|
-
},
|
|
67
61
|
strict: {
|
|
68
62
|
type: "boolean",
|
|
69
63
|
description: t("cli.doctor.args.strict.description"),
|
|
@@ -95,6 +89,41 @@ var doctorCommand = defineCommand({
|
|
|
95
89
|
description: t("cli.doctor.args.client.description"),
|
|
96
90
|
default: "all",
|
|
97
91
|
valueHint: "cc|codex|cursor|all"
|
|
92
|
+
},
|
|
93
|
+
// v2.0.0-rc.24 TASK-10: --layer filter for the cite contract audit. Pairs
|
|
94
|
+
// with --cite-coverage. Validated against {'team','personal','all'} at
|
|
95
|
+
// command entry; rejects 'both' (rc.20 plan-context vocabulary) explicitly.
|
|
96
|
+
layer: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: t("cli.doctor.args.layer.description"),
|
|
99
|
+
default: "all",
|
|
100
|
+
valueHint: "team|personal|all"
|
|
101
|
+
},
|
|
102
|
+
// rc.23 TASK-007 (a-C2): description-grade back-fill flag set. Read-side
|
|
103
|
+
// by default; `--auto` flips the writer arm on. Mutually exclusive with
|
|
104
|
+
// --fix / --fix-knowledge / --cite-coverage (different mutation surfaces).
|
|
105
|
+
"enrich-descriptions": {
|
|
106
|
+
type: "boolean",
|
|
107
|
+
description: t("cli.doctor.args.enrich-descriptions.description"),
|
|
108
|
+
default: false
|
|
109
|
+
},
|
|
110
|
+
auto: {
|
|
111
|
+
type: "boolean",
|
|
112
|
+
description: t("cli.doctor.args.auto.description"),
|
|
113
|
+
default: false
|
|
114
|
+
},
|
|
115
|
+
"dry-run": {
|
|
116
|
+
type: "boolean",
|
|
117
|
+
description: t("cli.doctor.args.dry-run.description"),
|
|
118
|
+
default: false
|
|
119
|
+
},
|
|
120
|
+
// v2.0.0-rc.25 TASK-10: --archive-history flag (parallel to rc.20
|
|
121
|
+
// --cite-coverage). Read-only; reads session_archive_attempted events
|
|
122
|
+
// and renders a per-session table. Pairs with the shared `--since` flag.
|
|
123
|
+
"archive-history": {
|
|
124
|
+
type: "boolean",
|
|
125
|
+
description: t("cli.doctor.args.archive-history.description"),
|
|
126
|
+
default: false
|
|
98
127
|
}
|
|
99
128
|
},
|
|
100
129
|
async run({ args }) {
|
|
@@ -111,8 +140,53 @@ var doctorCommand = defineCommand({
|
|
|
111
140
|
}
|
|
112
141
|
const fixKnowledge = args["fix-knowledge"] === true;
|
|
113
142
|
const fix = args.fix === true;
|
|
114
|
-
const rescan = args.rescan === true;
|
|
115
143
|
const citeCoverage = args["cite-coverage"] === true;
|
|
144
|
+
const enrichDesc = args["enrich-descriptions"] === true;
|
|
145
|
+
const archiveHistory = args["archive-history"] === true;
|
|
146
|
+
if (archiveHistory) {
|
|
147
|
+
if (fix || fixKnowledge || citeCoverage || enrichDesc) {
|
|
148
|
+
writeStderr(t("cli.doctor.errors.archive-history-mutex"));
|
|
149
|
+
process.exitCode = 1;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const sinceInput = args.since ?? "7d";
|
|
153
|
+
let sinceMs;
|
|
154
|
+
try {
|
|
155
|
+
sinceMs = parseSinceDuration(sinceInput);
|
|
156
|
+
} catch {
|
|
157
|
+
writeStderr(t("cli.doctor.errors.invalid-since", { input: sinceInput }));
|
|
158
|
+
process.exitCode = 1;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const report2 = await runDoctorArchiveHistory(resolution.target, {
|
|
162
|
+
since: sinceMs
|
|
163
|
+
});
|
|
164
|
+
if (args.json === true) {
|
|
165
|
+
writeStdout(JSON.stringify(report2, null, 2));
|
|
166
|
+
} else {
|
|
167
|
+
renderArchiveHistoryReport(report2, sinceInput);
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (enrichDesc) {
|
|
172
|
+
if (fix || fixKnowledge || citeCoverage) {
|
|
173
|
+
writeStderr(t("cli.doctor.errors.enrich-descriptions-mutex"));
|
|
174
|
+
process.exitCode = 1;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const autoFlag = args.auto === true;
|
|
178
|
+
const dryRun = args["dry-run"] === true;
|
|
179
|
+
const report2 = await enrichDescriptions(resolution.target, {
|
|
180
|
+
auto: autoFlag,
|
|
181
|
+
dryRun
|
|
182
|
+
});
|
|
183
|
+
if (args.json === true) {
|
|
184
|
+
writeStdout(JSON.stringify(report2, null, 2));
|
|
185
|
+
} else {
|
|
186
|
+
renderEnrichDescriptionsReport(report2);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
116
190
|
if (citeCoverage) {
|
|
117
191
|
if (fix || fixKnowledge) {
|
|
118
192
|
writeStderr(t("cli.doctor.errors.cite-coverage-mutex"));
|
|
@@ -133,9 +207,16 @@ var doctorCommand = defineCommand({
|
|
|
133
207
|
process.exitCode = 1;
|
|
134
208
|
return;
|
|
135
209
|
}
|
|
210
|
+
const layerFilter = args.layer ?? "all";
|
|
211
|
+
if (!isValidLayerFilter(layerFilter)) {
|
|
212
|
+
writeStderr(t("cli.doctor.errors.invalid-layer", { input: layerFilter }));
|
|
213
|
+
process.exitCode = 1;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
136
216
|
const report2 = await runDoctorCiteCoverage(resolution.target, {
|
|
137
217
|
since: sinceMs,
|
|
138
|
-
client: clientFilter
|
|
218
|
+
client: clientFilter,
|
|
219
|
+
layer: layerFilter
|
|
139
220
|
});
|
|
140
221
|
renderCiteCoverageReport(report2, args.json === true);
|
|
141
222
|
return;
|
|
@@ -145,9 +226,6 @@ var doctorCommand = defineCommand({
|
|
|
145
226
|
process.exitCode = 1;
|
|
146
227
|
return;
|
|
147
228
|
}
|
|
148
|
-
if (rescan) {
|
|
149
|
-
await runInitScan(resolution.target, { source: "doctor-rescan" });
|
|
150
|
-
}
|
|
151
229
|
let fixKnowledgeReport = null;
|
|
152
230
|
let fixReport = null;
|
|
153
231
|
let report;
|
|
@@ -350,6 +428,14 @@ var CITE_COVERAGE_CLIENT_FILTERS = /* @__PURE__ */ new Set([
|
|
|
350
428
|
function isValidClientFilter(input) {
|
|
351
429
|
return CITE_COVERAGE_CLIENT_FILTERS.has(input);
|
|
352
430
|
}
|
|
431
|
+
var CITE_COVERAGE_LAYER_FILTERS = /* @__PURE__ */ new Set([
|
|
432
|
+
"team",
|
|
433
|
+
"personal",
|
|
434
|
+
"all"
|
|
435
|
+
]);
|
|
436
|
+
function isValidLayerFilter(input) {
|
|
437
|
+
return CITE_COVERAGE_LAYER_FILTERS.has(input);
|
|
438
|
+
}
|
|
353
439
|
function renderCiteCoverageReport(report, jsonMode) {
|
|
354
440
|
if (jsonMode) {
|
|
355
441
|
writeStdout(JSON.stringify(report, null, 2));
|
|
@@ -392,8 +478,115 @@ function renderCiteCoverageReport(report, jsonMode) {
|
|
|
392
478
|
lines.push(` ${label}: ${count}`);
|
|
393
479
|
}
|
|
394
480
|
}
|
|
481
|
+
if (report.none_reason_histogram !== void 0 && Object.keys(report.none_reason_histogram).length > 0) {
|
|
482
|
+
lines.push("");
|
|
483
|
+
lines.push(`### ${t("doctor.cite.section.noneReasons")}`);
|
|
484
|
+
for (const [reason, count] of Object.entries(report.none_reason_histogram)) {
|
|
485
|
+
const label = t(`doctor.cite.none.${reason}`);
|
|
486
|
+
lines.push(` ${label}: ${count}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
appendContractSection(lines, report);
|
|
395
490
|
writeStdout(lines.join("\n"));
|
|
396
491
|
}
|
|
492
|
+
function appendContractSection(lines, report) {
|
|
493
|
+
const status = report.contract_metrics_status;
|
|
494
|
+
if (status === void 0) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const metrics = report.contract_metrics;
|
|
498
|
+
const perLayerType = report.per_layer_type;
|
|
499
|
+
const allCountsZero = metrics === void 0 || metrics.decisions_cited === 0 && metrics.pitfalls_cited === 0 && metrics.contract_with === 0 && metrics.contract_missing === 0 && metrics.hard_violated === 0 && metrics.cite_id_unresolved === 0 && Object.keys(metrics.skip_count).length === 0;
|
|
500
|
+
if (status === "awaiting_marker" && allCountsZero) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
lines.push("");
|
|
504
|
+
lines.push(`### ${t("cite-coverage.contract.header")}`);
|
|
505
|
+
if (status === "skipped:bootstrap_drift") {
|
|
506
|
+
lines.push(` ${t("cite-coverage.contract.status.skipped_bootstrap_drift")}`);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const statusKey = status === "ok" ? "cite-coverage.contract.status.ok" : "cite-coverage.contract.status.awaiting_marker";
|
|
510
|
+
lines.push(` status: ${t(statusKey)}`);
|
|
511
|
+
if (typeof report.contract_marker_ts === "number" && report.contract_marker_ts > 0) {
|
|
512
|
+
lines.push(` since: ${new Date(report.contract_marker_ts).toISOString()}`);
|
|
513
|
+
}
|
|
514
|
+
if (report.layer_filter !== void 0) {
|
|
515
|
+
lines.push(` layer filter: ${report.layer_filter}`);
|
|
516
|
+
}
|
|
517
|
+
if (metrics !== void 0) {
|
|
518
|
+
lines.push(` ${t("cite-coverage.contract.decisions_cited")}: ${metrics.decisions_cited}`);
|
|
519
|
+
lines.push(` ${t("cite-coverage.contract.pitfalls_cited")}: ${metrics.pitfalls_cited}`);
|
|
520
|
+
lines.push(` ${t("cite-coverage.contract.with")}: ${metrics.contract_with}`);
|
|
521
|
+
lines.push(` ${t("cite-coverage.contract.missing")}: ${metrics.contract_missing}`);
|
|
522
|
+
if (metrics.hard_violated > 0) {
|
|
523
|
+
const layerSuffix = report.layer_filter === "personal" ? t("cite-coverage.layer.personal_fyi") : t("cite-coverage.layer.team_review");
|
|
524
|
+
lines.push(
|
|
525
|
+
` ${t("cite-coverage.contract.hard_violated")} ${layerSuffix}: ${metrics.hard_violated}`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (perLayerType !== void 0) {
|
|
530
|
+
const teamKeys = Object.keys(perLayerType.team).filter(
|
|
531
|
+
(k) => perLayerType.team[k] > 0
|
|
532
|
+
);
|
|
533
|
+
const personalKeys = Object.keys(perLayerType.personal).filter(
|
|
534
|
+
(k) => perLayerType.personal[k] > 0
|
|
535
|
+
);
|
|
536
|
+
if (teamKeys.length > 0 || personalKeys.length > 0) {
|
|
537
|
+
lines.push("");
|
|
538
|
+
lines.push(`#### ${t("cite-coverage.layer.team")} \xD7 ${t("cite-coverage.layer.personal")}`);
|
|
539
|
+
for (const key of teamKeys) {
|
|
540
|
+
const label = t(`cite-coverage.contract.type.${key}`);
|
|
541
|
+
lines.push(` ${t("cite-coverage.layer.team")} \u2014 ${label}: ${perLayerType.team[key]}`);
|
|
542
|
+
}
|
|
543
|
+
for (const key of personalKeys) {
|
|
544
|
+
const label = t(`cite-coverage.contract.type.${key}`);
|
|
545
|
+
lines.push(
|
|
546
|
+
` ${t("cite-coverage.layer.personal")} \u2014 ${label}: ${perLayerType.personal[key]}`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (metrics !== void 0 && Object.keys(metrics.skip_count).length > 0) {
|
|
552
|
+
lines.push("");
|
|
553
|
+
lines.push(`#### ${t("cite-coverage.contract.skip_count")}`);
|
|
554
|
+
for (const [reason, count] of Object.entries(metrics.skip_count)) {
|
|
555
|
+
const label = t(`cite-coverage.skip.${reason}`);
|
|
556
|
+
lines.push(` ${label}: ${count}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (metrics !== void 0 && metrics.cite_id_unresolved > 0) {
|
|
560
|
+
lines.push("");
|
|
561
|
+
lines.push(
|
|
562
|
+
`${symbol.warn} ${t("cite-coverage.contract.cite_id_unresolved")}: ${metrics.cite_id_unresolved}`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function renderEnrichDescriptionsReport(report) {
|
|
567
|
+
const header = `${symbol.ok} ${paint.ai("fab doctor --enrich-descriptions")} mode=${report.mode}${report.dryRun ? " (dry-run)" : ""} scanned=${report.scanned} modified=${report.modified} skipped=${report.skipped}`;
|
|
568
|
+
writeStdout(header);
|
|
569
|
+
if (report.candidates.length === 0) {
|
|
570
|
+
writeStdout(t("doctor.enrich.allComplete"));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
writeStdout("");
|
|
574
|
+
for (const candidate of report.candidates) {
|
|
575
|
+
if (candidate.error !== void 0) {
|
|
576
|
+
writeStdout(`${symbol.error} ${candidate.path} \u2014 ${candidate.error}`);
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
const missing = candidate.missing.join(", ");
|
|
580
|
+
if (candidate.modified) {
|
|
581
|
+
const added = candidate.added_fields.join(", ");
|
|
582
|
+
writeStdout(
|
|
583
|
+
`${symbol.ok} ${candidate.path} \u2014 missing: ${missing} \u2192 added: ${added}`
|
|
584
|
+
);
|
|
585
|
+
} else {
|
|
586
|
+
writeStdout(`${symbol.warn} ${candidate.path} \u2014 missing: ${missing}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
397
590
|
function parseSinceDuration(input) {
|
|
398
591
|
const trimmed = input.trim();
|
|
399
592
|
if (trimmed.length === 0) {
|
|
@@ -418,6 +611,44 @@ function parseSinceDuration(input) {
|
|
|
418
611
|
}
|
|
419
612
|
throw new Error(`invalid --since value: ${input}`);
|
|
420
613
|
}
|
|
614
|
+
function renderArchiveHistoryReport(report, sinceLabel) {
|
|
615
|
+
if (report.entries.length === 0) {
|
|
616
|
+
writeStdout(t("doctor.archive-history.empty", { sinceLabel }));
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const lines = [];
|
|
620
|
+
lines.push(
|
|
621
|
+
t("doctor.archive-history.header", {
|
|
622
|
+
sinceLabel,
|
|
623
|
+
count: String(report.total),
|
|
624
|
+
plural: report.total === 1 ? "" : "s"
|
|
625
|
+
})
|
|
626
|
+
);
|
|
627
|
+
lines.push("");
|
|
628
|
+
lines.push(
|
|
629
|
+
`| ${t("doctor.archive-history.table.session")} | ${t(
|
|
630
|
+
"doctor.archive-history.table.lastAttempt"
|
|
631
|
+
)} | ${t("doctor.archive-history.table.outcome")} | ${t(
|
|
632
|
+
"doctor.archive-history.table.candidates"
|
|
633
|
+
)} | ${t("doctor.archive-history.table.coveredGap")} |`
|
|
634
|
+
);
|
|
635
|
+
lines.push("| ------- | ---------------- | -------- | ---------- | ----------- |");
|
|
636
|
+
for (const entry of report.entries) {
|
|
637
|
+
const lastAttempt = formatTimestampForTable(entry.last_attempted_at);
|
|
638
|
+
lines.push(
|
|
639
|
+
`| ${entry.session_id_short} | ${lastAttempt} | ${entry.outcome} | ${entry.candidates_proposed} | ${entry.age_since_covered_hours}h |`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
writeStdout(lines.join("\n"));
|
|
643
|
+
}
|
|
644
|
+
function formatTimestampForTable(iso) {
|
|
645
|
+
const d = new Date(iso);
|
|
646
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
647
|
+
const pad = (n) => n < 10 ? `0${n}` : `${n}`;
|
|
648
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(
|
|
649
|
+
d.getUTCHours()
|
|
650
|
+
)}:${pad(d.getUTCMinutes())}`;
|
|
651
|
+
}
|
|
421
652
|
export {
|
|
422
653
|
doctor_default as default,
|
|
423
654
|
doctorCommand,
|
package/dist/index.js
CHANGED
|
@@ -11,19 +11,22 @@ import { defineCommand, runMain } from "citty";
|
|
|
11
11
|
|
|
12
12
|
// src/commands/index.ts
|
|
13
13
|
var allCommands = {
|
|
14
|
-
install: () => import("./install-
|
|
15
|
-
doctor: () => import("./doctor-
|
|
16
|
-
serve: () => import("./serve-
|
|
17
|
-
uninstall: () => import("./uninstall-
|
|
18
|
-
config: () => import("./config-
|
|
19
|
-
"plan-context-hint": () => import("./plan-context-hint-
|
|
14
|
+
install: () => import("./install-S2J76N2B.js").then((module) => module.default),
|
|
15
|
+
doctor: () => import("./doctor-DXKPYPRC.js").then((module) => module.default),
|
|
16
|
+
serve: () => import("./serve-NPCI342P.js").then((module) => module.default),
|
|
17
|
+
uninstall: () => import("./uninstall-MQM6NUFM.js").then((module) => module.default),
|
|
18
|
+
config: () => import("./config-XGUUAYX6.js").then((module) => module.default),
|
|
19
|
+
"plan-context-hint": () => import("./plan-context-hint-KPGOW3QC.js").then((module) => module.default),
|
|
20
|
+
// v2.0.0-rc.23 TASK-014 (F8c): S5 onboard-slot coverage. Used by the
|
|
21
|
+
// fabric-archive Skill's first-run phase to detect unclaimed slots.
|
|
22
|
+
"onboard-coverage": () => import("./onboard-coverage-JJ5NGU7I.js").then((module) => module.default)
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
// src/index.ts
|
|
23
26
|
var main = defineCommand({
|
|
24
27
|
meta: {
|
|
25
28
|
name: "fabric",
|
|
26
|
-
version: "2.0.0-rc.
|
|
29
|
+
version: "2.0.0-rc.25",
|
|
27
30
|
description: t("cli.main.description")
|
|
28
31
|
},
|
|
29
32
|
subCommands: allCommands
|