@bussolabs/closeyourit-cli 0.25.1 → 0.27.0
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 +91 -4
- package/dist/base.d.ts +3 -18
- package/dist/base.js +16 -66
- package/dist/commands/kb/context.d.ts +12 -0
- package/dist/commands/kb/context.js +93 -0
- package/dist/commands/kb/doctor.d.ts +21 -0
- package/dist/commands/kb/doctor.js +107 -0
- package/dist/commands/kb/search.d.ts +1 -0
- package/dist/commands/kb/search.js +7 -0
- package/dist/commands/mcp.d.ts +10 -0
- package/dist/commands/mcp.js +33 -0
- package/dist/lib/api.d.ts +16 -0
- package/dist/lib/api.js +21 -1
- package/dist/lib/git.d.ts +17 -0
- package/dist/lib/git.js +56 -0
- package/dist/lib/knowledge-context.d.ts +27 -0
- package/dist/lib/knowledge-context.js +68 -0
- package/dist/lib/knowledge-doctor.d.ts +127 -0
- package/dist/lib/knowledge-doctor.js +310 -0
- package/dist/lib/knowledge.d.ts +30 -0
- package/dist/lib/knowledge.js +64 -0
- package/dist/lib/lookup.d.ts +30 -0
- package/dist/lib/lookup.js +82 -0
- package/dist/lib/mcp/protocol.d.ts +51 -0
- package/dist/lib/mcp/protocol.js +67 -0
- package/dist/lib/mcp/server.d.ts +41 -0
- package/dist/lib/mcp/server.js +221 -0
- package/dist/lib/mcp/tools.d.ts +43 -0
- package/dist/lib/mcp/tools.js +340 -0
- package/oclif.manifest.json +4276 -4081
- package/opencli.json +154 -2
- package/package.json +1 -1
package/dist/lib/api.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ export interface RequestOptions {
|
|
|
10
10
|
body?: unknown;
|
|
11
11
|
/** Defaults to true; set false for unauthenticated endpoints. */
|
|
12
12
|
auth?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Abort the request when the caller cannot afford to wait (CYCL-55): `kb context` runs at the
|
|
15
|
+
* start of every session, where a stalled connection would be paid on each one. Without it fetch
|
|
16
|
+
* waits as long as the OS lets it. An aborted call surfaces as a network ApiRequestError, like
|
|
17
|
+
* any other unreachable server.
|
|
18
|
+
*/
|
|
19
|
+
signal?: AbortSignal;
|
|
13
20
|
}
|
|
14
21
|
export interface Envelope<T = unknown> {
|
|
15
22
|
data: T;
|
|
@@ -42,6 +49,15 @@ export declare class CliApi {
|
|
|
42
49
|
* without a body is covered too. Reads never carry it: looking is not executing.
|
|
43
50
|
*/
|
|
44
51
|
confirm: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Interrompe ogni richiesta che parte da questo client, quando è ora di chiudere (CYCL-56).
|
|
54
|
+
*
|
|
55
|
+
* Vive sull'istanza per la stessa ragione di `confirm`: il server MCP deve poter mollare TUTTE le
|
|
56
|
+
* chiamate in volo — quelle degli strumenti e quelle delle ricerche per nome che si portano
|
|
57
|
+
* dietro — senza infilare un parametro in ogni firma che sta in mezzo. Una `signal` passata alla
|
|
58
|
+
* singola richiesta (`RequestOptions`) resta più specifica e vince su questa.
|
|
59
|
+
*/
|
|
60
|
+
signal?: AbortSignal;
|
|
45
61
|
constructor(config: CliConfig);
|
|
46
62
|
request<T = unknown>(method: HttpMethod, path: string, opts?: RequestOptions): Promise<Envelope<T>>;
|
|
47
63
|
/**
|
package/dist/lib/api.js
CHANGED
|
@@ -36,6 +36,15 @@ class CliApi {
|
|
|
36
36
|
* without a body is covered too. Reads never carry it: looking is not executing.
|
|
37
37
|
*/
|
|
38
38
|
confirm = false;
|
|
39
|
+
/**
|
|
40
|
+
* Interrompe ogni richiesta che parte da questo client, quando è ora di chiudere (CYCL-56).
|
|
41
|
+
*
|
|
42
|
+
* Vive sull'istanza per la stessa ragione di `confirm`: il server MCP deve poter mollare TUTTE le
|
|
43
|
+
* chiamate in volo — quelle degli strumenti e quelle delle ricerche per nome che si portano
|
|
44
|
+
* dietro — senza infilare un parametro in ogni firma che sta in mezzo. Una `signal` passata alla
|
|
45
|
+
* singola richiesta (`RequestOptions`) resta più specifica e vince su questa.
|
|
46
|
+
*/
|
|
47
|
+
signal;
|
|
39
48
|
constructor(config) {
|
|
40
49
|
this.config = config;
|
|
41
50
|
}
|
|
@@ -49,6 +58,7 @@ class CliApi {
|
|
|
49
58
|
method,
|
|
50
59
|
headers,
|
|
51
60
|
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
|
|
61
|
+
signal: opts.signal ?? this.signal,
|
|
52
62
|
});
|
|
53
63
|
}
|
|
54
64
|
catch (err) {
|
|
@@ -103,7 +113,17 @@ class CliApi {
|
|
|
103
113
|
}
|
|
104
114
|
/** Parse the response body and map the error envelope to an ApiRequestError. Shared by request() and upload(). */
|
|
105
115
|
async handleResponse(res) {
|
|
106
|
-
|
|
116
|
+
// The body can fail after the headers arrived: an abort that fires mid-read (RequestOptions.signal),
|
|
117
|
+
// a connection that drops. That is the same network failure as one on the first byte, so it must
|
|
118
|
+
// come out as an ApiRequestError — raw, it would escape every caller that maps failures by that
|
|
119
|
+
// type, and a command counting on a quiet failure would crash instead.
|
|
120
|
+
let text;
|
|
121
|
+
try {
|
|
122
|
+
text = await res.text();
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
throw new ApiRequestError(0, error_codes_1.ErrorCodes.Network.requestFailed, `Reading the response failed: ${err.message}`);
|
|
126
|
+
}
|
|
107
127
|
let json = {};
|
|
108
128
|
if (text.length > 0) {
|
|
109
129
|
try {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `owner/name` out of a git remote URL, in both shapes git writes them:
|
|
3
|
+
* git@github.com:bussolabs/closeyourit-cli.git
|
|
4
|
+
* https://github.com/bussolabs/closeyourit-cli
|
|
5
|
+
*
|
|
6
|
+
* Only the last two path segments are kept, so a nested group (gitlab) still yields the pair the
|
|
7
|
+
* project→repository map is keyed by. Anything that is not a pair — a local path, an empty remote —
|
|
8
|
+
* returns undefined: the caller then simply has no project, which is a legitimate state (CYCL-55,
|
|
9
|
+
* scenario 2), not an error.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseRepoSlug(url: string): string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* The `origin` remote of `cwd` as `owner/name`, or undefined when there is none to read: outside a
|
|
14
|
+
* repository, without an `origin`, or without git installed. Never throws and never waits long —
|
|
15
|
+
* this runs at the start of a session, where a hang would be felt on every single start.
|
|
16
|
+
*/
|
|
17
|
+
export declare function repoSlug(cwd: string, timeoutMs?: number): Promise<string | undefined>;
|
package/dist/lib/git.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseRepoSlug = parseRepoSlug;
|
|
4
|
+
exports.repoSlug = repoSlug;
|
|
5
|
+
const node_child_process_1 = require("node:child_process");
|
|
6
|
+
const node_util_1 = require("node:util");
|
|
7
|
+
const run = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
8
|
+
/**
|
|
9
|
+
* `owner/name` out of a git remote URL, in both shapes git writes them:
|
|
10
|
+
* git@github.com:bussolabs/closeyourit-cli.git
|
|
11
|
+
* https://github.com/bussolabs/closeyourit-cli
|
|
12
|
+
*
|
|
13
|
+
* Only the last two path segments are kept, so a nested group (gitlab) still yields the pair the
|
|
14
|
+
* project→repository map is keyed by. Anything that is not a pair — a local path, an empty remote —
|
|
15
|
+
* returns undefined: the caller then simply has no project, which is a legitimate state (CYCL-55,
|
|
16
|
+
* scenario 2), not an error.
|
|
17
|
+
*/
|
|
18
|
+
function parseRepoSlug(url) {
|
|
19
|
+
const trimmed = url.trim();
|
|
20
|
+
if (trimmed === '')
|
|
21
|
+
return undefined;
|
|
22
|
+
// A repository cloned from disk (`file:///srv/git/acme/api.git`) has no owner: keeping its last
|
|
23
|
+
// two directories would invent a slug that could collide with a real `owner/name`.
|
|
24
|
+
if (/^file:\/\//i.test(trimmed))
|
|
25
|
+
return undefined;
|
|
26
|
+
// With a scheme the path starts after the host; the scp-like form has everything past the colon.
|
|
27
|
+
const marker = trimmed.indexOf('://');
|
|
28
|
+
const path = marker === -1
|
|
29
|
+
? trimmed.slice(trimmed.indexOf(':') + 1)
|
|
30
|
+
: trimmed
|
|
31
|
+
.slice(marker + 3)
|
|
32
|
+
.split('/')
|
|
33
|
+
.slice(1)
|
|
34
|
+
.join('/');
|
|
35
|
+
if (marker === -1 && !trimmed.includes(':'))
|
|
36
|
+
return undefined;
|
|
37
|
+
const parts = path
|
|
38
|
+
.replace(/\.git$/i, '')
|
|
39
|
+
.split('/')
|
|
40
|
+
.filter((part) => part !== '');
|
|
41
|
+
return parts.length < 2 ? undefined : parts.slice(-2).join('/');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The `origin` remote of `cwd` as `owner/name`, or undefined when there is none to read: outside a
|
|
45
|
+
* repository, without an `origin`, or without git installed. Never throws and never waits long —
|
|
46
|
+
* this runs at the start of a session, where a hang would be felt on every single start.
|
|
47
|
+
*/
|
|
48
|
+
async function repoSlug(cwd, timeoutMs = 2000) {
|
|
49
|
+
try {
|
|
50
|
+
const { stdout } = await run('git', ['remote', 'get-url', 'origin'], { cwd, timeout: timeoutMs });
|
|
51
|
+
return parseRepoSlug(stdout);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ApiRequestError, type CliApi, type Envelope, type RequestOptions } from './api';
|
|
2
|
+
/**
|
|
3
|
+
* Does this failure mean the server has no such route? A 404 says so outright; a 500 only when the
|
|
4
|
+
* body was not an error envelope the backend wrote, which is how a missing route surfaces here.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isMissingRoute(error: ApiRequestError): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Ask for the project's knowledge context, falling back to its plain page list on a server that
|
|
9
|
+
* does not expose the context route yet (CYRA-767): the same short list, one call later, instead
|
|
10
|
+
* of a session start that reports a failure the reader can do nothing about.
|
|
11
|
+
*
|
|
12
|
+
* A 500 counts as "route missing" alongside 404, but only when the body was not a backend error
|
|
13
|
+
* envelope: the CLI asks for `Accept: application/json`, a server without the route renders its
|
|
14
|
+
* not-found page, has no JSON template for it, and fails — which arrives here as a plain
|
|
15
|
+
* `Network.requestFailed`. A 500 the backend narrated itself (`R500-…`) is a real fault of a route
|
|
16
|
+
* that exists, and retrying elsewhere would only replace its message with a more confusing one.
|
|
17
|
+
*/
|
|
18
|
+
export declare function fetchKnowledgeContext(api: CliApi, project: string, limit: number, opts?: RequestOptions): Promise<Envelope<unknown>>;
|
|
19
|
+
/**
|
|
20
|
+
* The project of `cwd`, matched on its `origin` remote through the project → repository map the
|
|
21
|
+
* backend already publishes (`projects github-map`).
|
|
22
|
+
*
|
|
23
|
+
* The remote was preferred over a local configuration file: it is already written in every
|
|
24
|
+
* repository, so nothing new has to be created, kept in sync, or forgotten in a fresh clone.
|
|
25
|
+
* No remote, no match, no repository at all → undefined, and the caller shows nothing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function projectFromGitRemote(api: CliApi, cwd: string, timeoutMs: number, opts?: RequestOptions): Promise<string | undefined>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isMissingRoute = isMissingRoute;
|
|
4
|
+
exports.fetchKnowledgeContext = fetchKnowledgeContext;
|
|
5
|
+
exports.projectFromGitRemote = projectFromGitRemote;
|
|
6
|
+
const api_1 = require("./api");
|
|
7
|
+
const error_codes_1 = require("../errors/error-codes");
|
|
8
|
+
const git_1 = require("./git");
|
|
9
|
+
const knowledge_1 = require("./knowledge");
|
|
10
|
+
/**
|
|
11
|
+
* Does this failure mean the server has no such route? A 404 says so outright; a 500 only when the
|
|
12
|
+
* body was not an error envelope the backend wrote, which is how a missing route surfaces here.
|
|
13
|
+
*/
|
|
14
|
+
function isMissingRoute(error) {
|
|
15
|
+
if (error.status === 404)
|
|
16
|
+
return true;
|
|
17
|
+
return error.status === 500 && error.code === error_codes_1.ErrorCodes.Network.requestFailed;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ask for the project's knowledge context, falling back to its plain page list on a server that
|
|
21
|
+
* does not expose the context route yet (CYRA-767): the same short list, one call later, instead
|
|
22
|
+
* of a session start that reports a failure the reader can do nothing about.
|
|
23
|
+
*
|
|
24
|
+
* A 500 counts as "route missing" alongside 404, but only when the body was not a backend error
|
|
25
|
+
* envelope: the CLI asks for `Accept: application/json`, a server without the route renders its
|
|
26
|
+
* not-found page, has no JSON template for it, and fails — which arrives here as a plain
|
|
27
|
+
* `Network.requestFailed`. A 500 the backend narrated itself (`R500-…`) is a real fault of a route
|
|
28
|
+
* that exists, and retrying elsewhere would only replace its message with a more confusing one.
|
|
29
|
+
*/
|
|
30
|
+
async function fetchKnowledgeContext(api, project, limit, opts = {}) {
|
|
31
|
+
const query = new URLSearchParams({ project, per: String(limit) });
|
|
32
|
+
try {
|
|
33
|
+
return await api.get(`/cli/v1/knowledge/context?${query.toString()}`, opts);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (!(error instanceof api_1.ApiRequestError) || !isMissingRoute(error))
|
|
37
|
+
throw error;
|
|
38
|
+
const pages = (0, knowledge_1.buildPagesQuery)({ kinds: [], page: 1, per: limit, project });
|
|
39
|
+
return api.get(`/cli/v1/knowledge/pages?${pages}`, opts);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The project of `cwd`, matched on its `origin` remote through the project → repository map the
|
|
44
|
+
* backend already publishes (`projects github-map`).
|
|
45
|
+
*
|
|
46
|
+
* The remote was preferred over a local configuration file: it is already written in every
|
|
47
|
+
* repository, so nothing new has to be created, kept in sync, or forgotten in a fresh clone.
|
|
48
|
+
* No remote, no match, no repository at all → undefined, and the caller shows nothing.
|
|
49
|
+
*/
|
|
50
|
+
async function projectFromGitRemote(api, cwd, timeoutMs, opts = {}) {
|
|
51
|
+
const slug = await (0, git_1.repoSlug)(cwd, timeoutMs);
|
|
52
|
+
if (slug === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
const wanted = slug.toLowerCase();
|
|
55
|
+
let page = 1;
|
|
56
|
+
let totalPages = 1;
|
|
57
|
+
do {
|
|
58
|
+
const query = new URLSearchParams({ page: String(page), per: '100', connected_only: 'true' });
|
|
59
|
+
// eslint-disable-next-line no-await-in-loop
|
|
60
|
+
const res = await api.get(`/cli/v1/projects/github-map?${query.toString()}`, opts);
|
|
61
|
+
const match = (res.data ?? []).find((row) => (row.repository?.full_name ?? '').toLowerCase() === wanted);
|
|
62
|
+
if (match)
|
|
63
|
+
return match.project_key ?? match.project_id;
|
|
64
|
+
totalPages = Number(res.meta?.total_pages ?? 1);
|
|
65
|
+
page += 1;
|
|
66
|
+
} while (page <= totalPages);
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { CliApi } from './api';
|
|
2
|
+
/**
|
|
3
|
+
* Il controllo di salute della conoscenza (`cyi kb doctor`, CYCL-57).
|
|
4
|
+
*
|
|
5
|
+
* Le verifiche girano QUI, sul client, sopra l'elenco delle pagine che il servizio già espone: è
|
|
6
|
+
* lo stesso canale su cui gira oggi lo script di audit tenuto fuori dal prodotto
|
|
7
|
+
* (`knowledge-base/scripts/audit_closeyourit_kb.rb`, che chiama `cyi kb list --status all --json`),
|
|
8
|
+
* e portarlo dentro il comando è tutto il punto del ticket — un controllo che nessuno lancia non
|
|
9
|
+
* accorge nessuno di niente. Il controllo del file versionato, poi, può girare solo da questa
|
|
10
|
+
* parte: il repo dei documenti sta sul disco di chi lavora, il servizio non lo vede.
|
|
11
|
+
*
|
|
12
|
+
* Nessuna verifica scrive: il modulo legge pagine e restituisce un referto. La riparazione resta
|
|
13
|
+
* un gesto esplicito di chi legge, ed è per questo che ogni verifica porta con sé il comando da
|
|
14
|
+
* dare — non lo esegue.
|
|
15
|
+
*/
|
|
16
|
+
/** Esito di una verifica. Le stringhe sono quelle che `statusDot` sa già colorare. */
|
|
17
|
+
export type DoctorCheckStatus = 'fail' | 'ok' | 'skipped' | 'warning';
|
|
18
|
+
/** Una pagina segnalata da una verifica: id e titolo per ritrovarla, `detail` per il perché. */
|
|
19
|
+
export interface DoctorFinding {
|
|
20
|
+
id: string;
|
|
21
|
+
title: string;
|
|
22
|
+
detail?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Il referto di una singola verifica: cosa guarda, com'è andata, cosa fare adesso. */
|
|
25
|
+
export interface DoctorCheck {
|
|
26
|
+
code: string;
|
|
27
|
+
status: DoctorCheckStatus;
|
|
28
|
+
count: number;
|
|
29
|
+
summary: string;
|
|
30
|
+
next_step: string;
|
|
31
|
+
pages: DoctorFinding[];
|
|
32
|
+
}
|
|
33
|
+
/** Una pagina come la serializza il servizio (KnowledgePageSerializer), per quel che serve qui. */
|
|
34
|
+
export interface DoctorPage extends Record<string, unknown> {
|
|
35
|
+
id?: unknown;
|
|
36
|
+
title?: unknown;
|
|
37
|
+
body?: unknown;
|
|
38
|
+
status?: unknown;
|
|
39
|
+
projects?: unknown;
|
|
40
|
+
groups?: unknown;
|
|
41
|
+
source_path?: unknown;
|
|
42
|
+
consolidated_at?: unknown;
|
|
43
|
+
updated_at?: unknown;
|
|
44
|
+
created_at?: unknown;
|
|
45
|
+
}
|
|
46
|
+
/** Il referto completo, nella forma che finisce sia a schermo sia in `--json`. */
|
|
47
|
+
export interface DoctorReport {
|
|
48
|
+
scanned: number;
|
|
49
|
+
truncated: boolean;
|
|
50
|
+
stale_days: number;
|
|
51
|
+
checks: DoctorCheck[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* I codici delle verifiche, nell'ordine in cui vengono mostrate: prima ciò che rende una pagina
|
|
55
|
+
* introvabile, poi ciò che spezza i collegamenti, infine le code ferme e il repo dei documenti.
|
|
56
|
+
* Codici stabili: chi ne legge l'esito da un programma può filtrare per codice senza inseguire
|
|
57
|
+
* il testo, che è per le persone.
|
|
58
|
+
*/
|
|
59
|
+
export declare const DOCTOR_CHECK_CODES: readonly ["empty-body", "orphan-scope", "duplicate-title", "dangling-link", "stale-review", "stale-consolidation", "inconsistent-consolidation", "missing-source-file"];
|
|
60
|
+
/**
|
|
61
|
+
* La chiave con cui il servizio risolve un wikilink verso una pagina: titolo senza spazi ai bordi,
|
|
62
|
+
* minuscolo (`LOWER(BTRIM(title))` in Knowledge::Links::Sync). Deliberatamente NON normalizza gli
|
|
63
|
+
* spazi interni né gli accenti: due titoli che il servizio considera diversi devono restare diversi
|
|
64
|
+
* anche qui, o il referto segnalerebbe collegamenti rotti che rotti non sono.
|
|
65
|
+
*/
|
|
66
|
+
export declare function titleKey(value: unknown): string;
|
|
67
|
+
/**
|
|
68
|
+
* Il markdown senza le parti di codice: blocchi recintati (``` / ~~~) e code span inline.
|
|
69
|
+
* Una guida che documenta la sintassi scrivendo `[[Titolo]]` fra i backtick non sta collegando
|
|
70
|
+
* niente, e il servizio infatti non la collega (Knowledge::Links::Segments).
|
|
71
|
+
*/
|
|
72
|
+
export declare function stripCode(markdown: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* I titoli citati come wikilink nel corpo, deduplicati senza distinzione di maiuscole e nell'ordine
|
|
75
|
+
* di apparizione — la stessa lettura che fa il servizio quando costruisce il grafo dei collegamenti.
|
|
76
|
+
*/
|
|
77
|
+
export declare function wikilinkTitles(body: unknown): string[];
|
|
78
|
+
export interface DoctorInput {
|
|
79
|
+
/** Le pagine visibili, in ogni stato. */
|
|
80
|
+
pages: DoctorPage[];
|
|
81
|
+
/** Le accettate non ancora archiviate, come le elenca il servizio (scope `awaiting_consolidation`). */
|
|
82
|
+
awaitingConsolidation: DoctorPage[];
|
|
83
|
+
/** Da quanti giorni una coda ferma va segnalata. */
|
|
84
|
+
staleDays: number;
|
|
85
|
+
now: Date;
|
|
86
|
+
/** La radice del repo dei documenti versionati; assente → il controllo del file non si fa. */
|
|
87
|
+
docsRoot?: string;
|
|
88
|
+
/** Come si guarda se un file c'è. Iniettabile perché le verifiche restino pure e testabili. */
|
|
89
|
+
fileExists?: (path: string) => boolean;
|
|
90
|
+
/**
|
|
91
|
+
* L'elenco letto è quello di UN progetto. Il servizio però risolve i wikilink e l'unicità dei
|
|
92
|
+
* titoli su TUTTA l'organizzazione (Knowledge::Links::Sync), quindi da un elenco ristretto quelle
|
|
93
|
+
* due verifiche direbbero il falso in entrambi i versi: collegamenti sani segnalati come appesi
|
|
94
|
+
* perché la destinazione sta in un altro progetto, e doppioni cross-progetto mai visti.
|
|
95
|
+
*/
|
|
96
|
+
scopedToProject?: boolean;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Tutte le verifiche, sempre tutte e sempre nello stesso ordine: un referto che tace sulle prove
|
|
100
|
+
* superate non si distingue da un referto che non le ha fatte.
|
|
101
|
+
*/
|
|
102
|
+
export declare function runDoctorChecks(input: DoctorInput): DoctorCheck[];
|
|
103
|
+
/** Quante verifiche sono fallite e quante hanno solo avvisato. */
|
|
104
|
+
export declare function doctorTotals(checks: DoctorCheck[]): {
|
|
105
|
+
failures: number;
|
|
106
|
+
warnings: number;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Il referto a schermo: prima la tabella di tutte le verifiche con il loro esito, poi un blocco per
|
|
110
|
+
* ciascuna di quelle che hanno qualcosa da dire — la spiegazione, il passo da fare e le pagine.
|
|
111
|
+
*/
|
|
112
|
+
export declare function renderDoctorReport(report: DoctorReport): string;
|
|
113
|
+
/**
|
|
114
|
+
* Legge le pagine da esaminare, fermandosi a `limit` righe.
|
|
115
|
+
*
|
|
116
|
+
* Il tetto c'è perché un controllo che scandisce tutto il parco non deve poter diventare una
|
|
117
|
+
* scansione infinita lanciata in linea: quando si ferma prima della fine lo DICE (`truncated`), così
|
|
118
|
+
* il referto non fa passare per "tutto a posto" ciò che semplicemente non ha guardato.
|
|
119
|
+
*/
|
|
120
|
+
export declare function fetchDoctorPages(api: CliApi, opts: {
|
|
121
|
+
limit: number;
|
|
122
|
+
project?: string;
|
|
123
|
+
awaitingConsolidation?: boolean;
|
|
124
|
+
}): Promise<{
|
|
125
|
+
pages: DoctorPage[];
|
|
126
|
+
truncated: boolean;
|
|
127
|
+
}>;
|