@deftai/directive-core 0.84.0 → 0.86.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/dist/cache/fetch.d.ts +17 -0
- package/dist/cache/fetch.js +50 -0
- package/dist/doctor/checks.js +16 -1
- package/dist/doctor/constants.d.ts +4 -2
- package/dist/doctor/constants.js +10 -6
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.d.ts +2 -1
- package/dist/doctor/main.js +83 -22
- package/dist/doctor/npm-registry.d.ts +23 -0
- package/dist/doctor/npm-registry.js +128 -0
- package/dist/doctor/npm-view.d.ts +10 -0
- package/dist/doctor/npm-view.js +31 -0
- package/dist/doctor/payload-staleness.js +2 -9
- package/dist/doctor/signpost-checks.d.ts +2 -1
- package/dist/doctor/signpost-checks.js +2 -0
- package/dist/doctor/types.d.ts +12 -0
- package/dist/eval-health-relocation/evaluate.js +15 -1
- package/dist/fs/projection-containment.js +1 -1
- package/dist/hooks/cursor-hooks.d.ts +6 -20
- package/dist/hooks/cursor-hooks.js +7 -92
- package/dist/init-deposit/agent-hooks.d.ts +2 -2
- package/dist/init-deposit/agent-hooks.js +24 -38
- package/dist/init-deposit/gitignore.js +5 -0
- package/dist/init-deposit/hygiene.d.ts +22 -0
- package/dist/init-deposit/hygiene.js +136 -4
- package/dist/init-deposit/legacy-detect.d.ts +25 -1
- package/dist/init-deposit/legacy-detect.js +143 -11
- package/dist/init-deposit/refresh.d.ts +2 -0
- package/dist/init-deposit/refresh.js +18 -5
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +2 -0
- package/dist/policy/org-force-on-migration.d.ts +44 -0
- package/dist/policy/org-force-on-migration.js +242 -0
- package/dist/policy/product-signal.d.ts +3 -1
- package/dist/policy/product-signal.js +15 -4
- package/dist/policy/value-feedback.d.ts +18 -1
- package/dist/policy/value-feedback.js +78 -5
- package/dist/preflight-cache/evaluate.js +12 -1
- package/dist/product-signal/consent-prompt.d.ts +13 -0
- package/dist/product-signal/consent-prompt.js +30 -0
- package/dist/product-signal/submit.js +22 -1
- package/dist/render/framework-commands.d.ts +1 -1
- package/dist/render/framework-commands.js +6 -6
- package/dist/render/index.d.ts +1 -0
- package/dist/render/index.js +1 -0
- package/dist/render/roadmap-render.d.ts +5 -1
- package/dist/render/roadmap-render.js +20 -2
- package/dist/render/rule-map-template.d.ts +2 -0
- package/dist/render/rule-map-template.js +407 -0
- package/dist/render/rule-map.d.ts +2 -0
- package/dist/render/rule-map.js +708 -0
- package/dist/review-monitor/constants.d.ts +7 -1
- package/dist/review-monitor/constants.js +40 -14
- package/dist/review-monitor/github-lease.d.ts +45 -0
- package/dist/review-monitor/github-lease.js +106 -0
- package/dist/review-monitor/index.d.ts +2 -0
- package/dist/review-monitor/index.js +2 -0
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +152 -0
- package/dist/review-monitor/record.d.ts +61 -13
- package/dist/review-monitor/record.js +379 -132
- package/dist/review-monitor/verify.d.ts +2 -0
- package/dist/review-monitor/verify.js +32 -13
- package/dist/scm/gh-rest.d.ts +7 -1
- package/dist/scm/gh-rest.js +35 -0
- package/dist/session/session-start.js +5 -0
- package/dist/task-surface/index.js +53 -13
- package/dist/verify-env/agent-hooks-live-probe.d.ts +32 -0
- package/dist/verify-env/agent-hooks-live-probe.js +216 -0
- package/dist/verify-env/index.d.ts +1 -0
- package/dist/verify-env/index.js +1 -0
- package/dist/xbrief-migrate/constants.d.ts +6 -0
- package/dist/xbrief-migrate/constants.js +6 -0
- package/dist/xbrief-migrate/index.d.ts +2 -2
- package/dist/xbrief-migrate/index.js +2 -2
- package/dist/xbrief-migrate/migrate-project.d.ts +7 -0
- package/dist/xbrief-migrate/migrate-project.js +44 -18
- package/package.json +3 -3
package/dist/cache/fetch.d.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { type RunGhApiFn } from "../scm/gh-rest.js";
|
|
2
|
+
import { type Clock } from "./time.js";
|
|
2
3
|
import type { FetchAllReport, StateRefreshReport } from "./types.js";
|
|
4
|
+
/** Repo-level stamp proving a successful open-only enumeration (#2826). */
|
|
5
|
+
export declare const OPEN_INVENTORY_STAMP = "open-inventory.json";
|
|
6
|
+
export interface OpenInventoryStamp {
|
|
7
|
+
readonly fetched_at: string;
|
|
8
|
+
readonly open_count: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function openInventoryStampPath(cacheRoot: string, source: string, repo: string): string;
|
|
11
|
+
export declare function readOpenInventoryStamp(cacheRoot: string, source: string, repo: string): OpenInventoryStamp | null;
|
|
12
|
+
export declare function writeOpenInventoryStamp(options: {
|
|
13
|
+
cacheRoot: string;
|
|
14
|
+
source: string;
|
|
15
|
+
repo: string;
|
|
16
|
+
openCount: number;
|
|
17
|
+
fetchedAt?: Date;
|
|
18
|
+
clock?: Clock;
|
|
19
|
+
}): void;
|
|
3
20
|
export declare const REST_MAX_PER_PAGE = 100;
|
|
4
21
|
export declare const REST_PAGINATION_MAX_PAGES = 100;
|
|
5
22
|
export declare const DEFAULT_RETRY_AFTER_FALLBACK_S = 60;
|
package/dist/cache/fetch.js
CHANGED
|
@@ -4,9 +4,49 @@ import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/project
|
|
|
4
4
|
import { GhRestError, InvalidRepoError, restIssueView, runGhApi, } from "../scm/gh-rest.js";
|
|
5
5
|
import { DEFAULT_BATCH_SIZE, DEFAULT_DELAY_MS } from "./constants.js";
|
|
6
6
|
import { CacheError, CacheFetchError } from "./errors.js";
|
|
7
|
+
import { atomicWriteText } from "./io.js";
|
|
7
8
|
import { pythonJsonLine } from "./json.js";
|
|
8
9
|
import { cachePut, isFresh, validateRepo } from "./operations.js";
|
|
9
10
|
import { entryDir } from "./paths.js";
|
|
11
|
+
import { systemClock, utcIso } from "./time.js";
|
|
12
|
+
/** Repo-level stamp proving a successful open-only enumeration (#2826). */
|
|
13
|
+
export const OPEN_INVENTORY_STAMP = "open-inventory.json";
|
|
14
|
+
export function openInventoryStampPath(cacheRoot, source, repo) {
|
|
15
|
+
const [owner, name] = repo.split("/", 2);
|
|
16
|
+
return join(cacheRoot, source, owner, name, OPEN_INVENTORY_STAMP);
|
|
17
|
+
}
|
|
18
|
+
export function readOpenInventoryStamp(cacheRoot, source, repo) {
|
|
19
|
+
const path = openInventoryStampPath(cacheRoot, source, repo);
|
|
20
|
+
if (!existsSync(path))
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
24
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
25
|
+
return null;
|
|
26
|
+
const fetchedAt = raw.fetched_at;
|
|
27
|
+
const openCount = raw.open_count;
|
|
28
|
+
if (typeof fetchedAt !== "string")
|
|
29
|
+
return null;
|
|
30
|
+
if (typeof openCount !== "number" || openCount !== 0)
|
|
31
|
+
return null;
|
|
32
|
+
return { fetched_at: fetchedAt, open_count: openCount };
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function writeOpenInventoryStamp(options) {
|
|
39
|
+
if (options.openCount !== 0)
|
|
40
|
+
return;
|
|
41
|
+
validateRepo(options.repo);
|
|
42
|
+
const clock = options.clock ?? systemClock;
|
|
43
|
+
const fetchedAt = options.fetchedAt ?? clock.now();
|
|
44
|
+
const payload = {
|
|
45
|
+
fetched_at: utcIso(clock, fetchedAt),
|
|
46
|
+
open_count: 0,
|
|
47
|
+
};
|
|
48
|
+
atomicWriteText(openInventoryStampPath(options.cacheRoot, options.source, options.repo), `${JSON.stringify(payload)}\n`);
|
|
49
|
+
}
|
|
10
50
|
export const REST_MAX_PER_PAGE = 100;
|
|
11
51
|
export const REST_PAGINATION_MAX_PAGES = 100;
|
|
12
52
|
export const DEFAULT_RETRY_AFTER_FALLBACK_S = 60;
|
|
@@ -308,6 +348,16 @@ export function runFetchAll(options) {
|
|
|
308
348
|
if (processed % batchSize === 0)
|
|
309
349
|
maybeSleep(delayMs);
|
|
310
350
|
}
|
|
351
|
+
// #2826: zero open issues is a successful inventory answer — persist freshness
|
|
352
|
+
// metadata so preflight-cache does not treat minFetchedAt=null as Infinity stale.
|
|
353
|
+
if (total === 0 && report.issuesFailed === 0) {
|
|
354
|
+
writeOpenInventoryStamp({
|
|
355
|
+
cacheRoot,
|
|
356
|
+
source,
|
|
357
|
+
repo: options.repo,
|
|
358
|
+
openCount: 0,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
311
361
|
return report;
|
|
312
362
|
}
|
|
313
363
|
export function cacheFetchAll(options) {
|
package/dist/doctor/checks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { CANONICAL_GITIGNORE_BASELINE } from "../init-deposit/gitignore.js";
|
|
3
|
-
import { detectLegacyLayout, legacyLayoutSignpostLine, } from "../init-deposit/legacy-detect.js";
|
|
3
|
+
import { detectDualLayout, detectLegacyLayout, dualLayoutSignpostLine, legacyLayoutSignpostLine, } from "../init-deposit/legacy-detect.js";
|
|
4
4
|
import { detectCanonicalVendoredManifest, isNpmManaged, NPM_MANAGED_SENTINEL_KEY, NPM_MANAGED_SENTINEL_VALUE, } from "../init-deposit/migrate.js";
|
|
5
5
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
6
6
|
import { findSkillPathsInText } from "../text/redos-safe.js";
|
|
@@ -300,6 +300,21 @@ export function checkLegacyLayout(projectRoot, seams = {}) {
|
|
|
300
300
|
...(seams.isFile ? { isFile: seams.isFile } : {}),
|
|
301
301
|
...(seams.isDir ? { isDir: seams.isDir } : {}),
|
|
302
302
|
};
|
|
303
|
+
const dualLayout = detectDualLayout(projectRoot, legacySeams);
|
|
304
|
+
if (dualLayout !== null) {
|
|
305
|
+
return {
|
|
306
|
+
name: "legacy-layout",
|
|
307
|
+
status: "fail",
|
|
308
|
+
detail: dualLayoutSignpostLine(dualLayout),
|
|
309
|
+
data: {
|
|
310
|
+
legacy_layout: true,
|
|
311
|
+
legacy_layout_kind: dualLayout.kind,
|
|
312
|
+
evidence: [...dualLayout.evidence],
|
|
313
|
+
upgrading_doc_url: UPGRADING_DOC_URL,
|
|
314
|
+
go_bridge_releases_url: GO_BRIDGE_RELEASES_URL,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
303
318
|
const detection = detectLegacyLayout(projectRoot, legacySeams);
|
|
304
319
|
if (!detection.legacy) {
|
|
305
320
|
return {
|
|
@@ -10,11 +10,13 @@ export declare const TASKFILE_INCLUDE_SNIPPET = "version: '3'\n\nincludes:\n de
|
|
|
10
10
|
export declare const DOCTOR_ALLOWED_FLAGS: readonly ["--session", "--fix", "--repair", "--repair-taskfile", "--json", "--quiet", "--full", "--network", "--project-root", "-h", "--help"];
|
|
11
11
|
/** npm consumer deposit after #2022 Phase 3 -- Python scripts/ tree is intentionally absent. */
|
|
12
12
|
export declare const NPM_PACKAGE_NAME = "@deftai/directive";
|
|
13
|
+
export declare const PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
14
|
+
export declare const NPM_REGISTRY_MIRROR_DOC_URL = "https://github.com/deftai/directive/blob/master/content/UPGRADING.md#corporate-or-mirrored-npm-registry";
|
|
13
15
|
export declare const NETWORK_DISCLOSURE_LINE: string;
|
|
14
16
|
export declare const PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE: string;
|
|
15
|
-
export declare const EXPECTED_FRAMEWORK_DIRS: readonly ["tasks", "scripts", "
|
|
17
|
+
export declare const EXPECTED_FRAMEWORK_DIRS: readonly ["tasks", "scripts", "xbrief"];
|
|
16
18
|
/** npm consumer deposit after #2022 Phase 3 -- Python scripts/ tree is intentionally absent. */
|
|
17
|
-
export declare const CONSUMER_FRAMEWORK_DIRS: readonly ["tasks", "
|
|
19
|
+
export declare const CONSUMER_FRAMEWORK_DIRS: readonly ["tasks", "xbrief"];
|
|
18
20
|
export declare const DEFT_REPO_POSITIVE_MARKERS: readonly ["content/templates/agents-entry.md", "content/skills/deft-directive-build/SKILL.md"];
|
|
19
21
|
export declare const EXPECTED_CONTENT_DIRS: readonly ["languages", "strategies", "skills", "templates"];
|
|
20
22
|
/** Post-freeze canonical upgrade path (#1997 / #2003 / #1912). */
|
package/dist/doctor/constants.js
CHANGED
|
@@ -28,12 +28,16 @@ export const DOCTOR_ALLOWED_FLAGS = [
|
|
|
28
28
|
];
|
|
29
29
|
/** npm consumer deposit after #2022 Phase 3 -- Python scripts/ tree is intentionally absent. */
|
|
30
30
|
export const NPM_PACKAGE_NAME = "@deftai/directive";
|
|
31
|
+
export const PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
32
|
+
export const NPM_REGISTRY_MIRROR_DOC_URL = `${UPGRADING_DOC_URL}#corporate-or-mirrored-npm-registry`;
|
|
31
33
|
// #2182: payload-staleness is the only doctor check that can reach a network
|
|
32
34
|
// endpoint (git ls-remote verifies the pinned ref and `npm view` compares a
|
|
33
|
-
// release-tag install with the latest stable package).
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
35
|
+
// release-tag install with the latest stable package). The #2808 baseline
|
|
36
|
+
// registry-routing check uses only offline `npm config get` reads.
|
|
37
|
+
// Payload-staleness is OFF by default (offline tier) and requires the explicit
|
|
38
|
+
// `--network` flag; this line discloses exactly which tool + registry class it
|
|
39
|
+
// may contact BEFORE the check runs, and the skip line tells an offline run how
|
|
40
|
+
// to opt in.
|
|
37
41
|
export const NETWORK_DISCLOSURE_LINE = "[deft doctor] --network: this run may contact your git remote (framework " +
|
|
38
42
|
"repo host) and the npm registry (registry.npmjs.org) to " +
|
|
39
43
|
`look up the latest ${NPM_PACKAGE_NAME} version.`;
|
|
@@ -42,9 +46,9 @@ export const PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE = "skip -- offline tier (def
|
|
|
42
46
|
"(discloses tool + registry before contacting either).";
|
|
43
47
|
// Engine / lifecycle dirs that stay at the framework root (NOT relocated by
|
|
44
48
|
// #1875). Shippable-content dirs moved under content/ -- see EXPECTED_CONTENT_DIRS.
|
|
45
|
-
export const EXPECTED_FRAMEWORK_DIRS = ["tasks", "scripts", "
|
|
49
|
+
export const EXPECTED_FRAMEWORK_DIRS = ["tasks", "scripts", "xbrief"];
|
|
46
50
|
/** npm consumer deposit after #2022 Phase 3 -- Python scripts/ tree is intentionally absent. */
|
|
47
|
-
export const CONSUMER_FRAMEWORK_DIRS = ["tasks", "
|
|
51
|
+
export const CONSUMER_FRAMEWORK_DIRS = ["tasks", "xbrief"];
|
|
48
52
|
// Post-#1875 content/ move: these framework-internal markers now live under
|
|
49
53
|
// content/ in the SOURCE repo. They identify a deft source checkout (a consumer
|
|
50
54
|
// would never reproduce them); the C1 flatten means a consumer deposit has no
|
package/dist/doctor/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./flags.js";
|
|
|
6
6
|
export * from "./json.js";
|
|
7
7
|
export * from "./main.js";
|
|
8
8
|
export * from "./manifest.js";
|
|
9
|
+
export * from "./npm-registry.js";
|
|
9
10
|
export * from "./paths.js";
|
|
10
11
|
export * from "./payload-staleness.js";
|
|
11
12
|
export * from "./release-availability.js";
|
package/dist/doctor/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./flags.js";
|
|
|
6
6
|
export * from "./json.js";
|
|
7
7
|
export * from "./main.js";
|
|
8
8
|
export * from "./manifest.js";
|
|
9
|
+
export * from "./npm-registry.js";
|
|
9
10
|
export * from "./paths.js";
|
|
10
11
|
export * from "./payload-staleness.js";
|
|
11
12
|
export * from "./release-availability.js";
|
package/dist/doctor/main.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { type ResolutionFacts } from "../resolution/index.js";
|
|
|
2
2
|
import { createPlainSink } from "./output.js";
|
|
3
3
|
import type { DoctorSeams, Finding, ResolutionSummary } from "./types.js";
|
|
4
4
|
export declare function cmdDoctor(args: readonly string[], seams?: DoctorSeams): number;
|
|
5
|
-
export declare function runAgentHooksHealthCheck(projectRoot: string, consumerContext: boolean, sink: ReturnType<typeof createPlainSink>, addFinding: (finding: Finding) => void, seams: DoctorSeams):
|
|
5
|
+
export declare function runAgentHooksHealthCheck(projectRoot: string, consumerContext: boolean, sink: ReturnType<typeof createPlainSink>, addFinding: (finding: Finding) => void, seams: DoctorSeams): boolean;
|
|
6
|
+
export declare function runAgentHooksLiveProbeCheck(projectRoot: string, sink: ReturnType<typeof createPlainSink>, addFinding: (finding: Finding) => void, seams: DoctorSeams): void;
|
|
6
7
|
/**
|
|
7
8
|
* Never emit a bare `task ...` remediation in a project without Taskfile wiring
|
|
8
9
|
* (#2267). The `directive` surface always works; `task deft:X` is optional and
|
package/dist/doctor/main.js
CHANGED
|
@@ -7,6 +7,7 @@ import { loadProjectDefinition } from "../policy/resolve.js";
|
|
|
7
7
|
import { checkLocalEngineIntegrity, classify, detectPackageManager, evaluateSkew, reconcileVersions, plan as resolvePlan, } from "../resolution/index.js";
|
|
8
8
|
import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
|
|
9
9
|
import { evaluateAgentHooks } from "../verify-env/agent-hooks.js";
|
|
10
|
+
import { probeAgentHooksLive } from "../verify-env/agent-hooks-live-probe.js";
|
|
10
11
|
import { agentsRefreshPlan, hasV3ManagedMarker } from "./agents-md.js";
|
|
11
12
|
import { runChecks } from "./checks.js";
|
|
12
13
|
import { CONSUMER_FRAMEWORK_DIRS, EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, NETWORK_DISCLOSURE_LINE, PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
|
|
@@ -14,8 +15,9 @@ import { decideThrottle, formatIsoZ, readState, renderDoctorStatusLine, writeSta
|
|
|
14
15
|
import { formatAllowedFlagsHint, formatUnknownFlagsError, parseDoctorFlags } from "./flags.js";
|
|
15
16
|
import { pythonJsonDump } from "./json.js";
|
|
16
17
|
import { parseInstallRootFromAgentsMd } from "./manifest.js";
|
|
18
|
+
import { runNpmRegistryMirrorCheck } from "./npm-registry.js";
|
|
17
19
|
import { createPlainSink } from "./output.js";
|
|
18
|
-
import { readTextSafe,
|
|
20
|
+
import { readTextSafe, resolveFrameworkRootForProject, resolvePath, resolveVersion, runningInsideDeftRepo, } from "./paths.js";
|
|
19
21
|
import { runPayloadStalenessCheck } from "./payload-staleness.js";
|
|
20
22
|
import { runLocalSignpostChecks } from "./signpost-checks.js";
|
|
21
23
|
import { classifyTaskfileInclude, formatMissingIncludeSnippet, includesBlockHasDeftTaskfile, resolveConsumerTaskfile, } from "./taskfile.js";
|
|
@@ -56,7 +58,7 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
56
58
|
const quietMode = flags.quiet;
|
|
57
59
|
const fullMode = flags.full;
|
|
58
60
|
const projectRoot = resolvePath(flags.projectRoot ?? process.cwd());
|
|
59
|
-
const frameworkRoot = seams.frameworkRoot ??
|
|
61
|
+
const frameworkRoot = seams.frameworkRoot ?? resolveFrameworkRootForProject(projectRoot);
|
|
60
62
|
const consumerContext = resolve(projectRoot) !== resolve(frameworkRoot);
|
|
61
63
|
const whichFn = seams.whichFn ?? defaultWhich;
|
|
62
64
|
const nowFn = seams.now ?? (() => new Date());
|
|
@@ -90,7 +92,7 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
90
92
|
}
|
|
91
93
|
const signpostWarnings = throttleFindings.filter((f) => f.severity === "warning").length;
|
|
92
94
|
if (signpostWarnings > 0 && !jsonMode) {
|
|
93
|
-
throttleSink.finalWarn(`Signpost advisory: ${signpostWarnings} local
|
|
95
|
+
throttleSink.finalWarn(`Signpost advisory: ${signpostWarnings} local configuration / layout note(s) above (throttle-skipped full probe).`);
|
|
94
96
|
}
|
|
95
97
|
return decision.dirty ? 1 : 0;
|
|
96
98
|
}
|
|
@@ -164,13 +166,31 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
164
166
|
sink.blank();
|
|
165
167
|
}
|
|
166
168
|
sink.info("Checking agent-host hook registration...");
|
|
167
|
-
|
|
169
|
+
const findingCountBeforeHooks = findings.length;
|
|
170
|
+
const hooksHealthy = runAgentHooksHealthCheck(projectRoot, consumerContext, sink, addFinding, seams);
|
|
171
|
+
if (fullMode && hooksHealthy) {
|
|
172
|
+
const lastFinding = findings[findings.length - 1];
|
|
173
|
+
if (findings.length > findingCountBeforeHooks &&
|
|
174
|
+
lastFinding?.check === "agent-hooks-registration" &&
|
|
175
|
+
lastFinding?.status === "registered") {
|
|
176
|
+
findings.pop();
|
|
177
|
+
}
|
|
178
|
+
runAgentHooksLiveProbeCheck(projectRoot, sink, addFinding, seams);
|
|
179
|
+
}
|
|
180
|
+
if (consumerContext) {
|
|
181
|
+
if (!jsonMode) {
|
|
182
|
+
sink.blank();
|
|
183
|
+
}
|
|
184
|
+
sink.info("Checking npm registry routing...");
|
|
185
|
+
runNpmRegistryMirrorCheck(projectRoot, sink, addFinding, seams);
|
|
186
|
+
}
|
|
168
187
|
if (!jsonMode) {
|
|
169
188
|
sink.blank();
|
|
170
189
|
}
|
|
171
|
-
// #2182: payload-staleness is the only doctor check that can reach a
|
|
172
|
-
//
|
|
173
|
-
//
|
|
190
|
+
// #2182: payload-staleness is the only doctor check that can reach a network
|
|
191
|
+
// endpoint (git verifies the pin; npm compares stable release availability).
|
|
192
|
+
// The baseline #2808 registry-routing diagnostic above is an offline
|
|
193
|
+
// `npm config get` read. Payload-staleness stays in the OFFLINE
|
|
174
194
|
// tier (skipped) unless the operator explicitly opts into the NETWORK tier
|
|
175
195
|
// via `--network`, and the tool + registry class is disclosed BEFORE the
|
|
176
196
|
// check runs -- never silently, never as a side effect of a read-only
|
|
@@ -344,40 +364,81 @@ export function runAgentHooksHealthCheck(projectRoot, consumerContext, sink, add
|
|
|
344
364
|
const reason = "maintainer source checkout; project hook deposit is consumer-only";
|
|
345
365
|
sink.info(`${checkName}: skip -- ${reason}`);
|
|
346
366
|
addFinding({ severity: "skip", message: reason, check: checkName, status: "skip" });
|
|
347
|
-
return;
|
|
367
|
+
return false;
|
|
348
368
|
}
|
|
349
369
|
try {
|
|
350
370
|
const result = (seams.evaluateAgentHooks ?? evaluateAgentHooks)(projectRoot);
|
|
351
|
-
if (result.code
|
|
352
|
-
const message = `${checkName}:
|
|
353
|
-
|
|
354
|
-
sink.success(message);
|
|
371
|
+
if (result.code !== 0) {
|
|
372
|
+
const message = `${checkName}: ${result.message.replace(/\s+/g, " ").trim()}`;
|
|
373
|
+
sink.warn(message);
|
|
355
374
|
addFinding({
|
|
356
|
-
severity: "
|
|
375
|
+
severity: "warning",
|
|
357
376
|
message,
|
|
358
377
|
check: checkName,
|
|
359
|
-
status: "
|
|
378
|
+
status: result.code === 2 ? "unavailable" : "incomplete",
|
|
360
379
|
registrations: result.registrations,
|
|
361
|
-
|
|
362
|
-
trust_review: "Open `/hooks` in Codex and review the project hook commands.",
|
|
380
|
+
suggestion: "deft update",
|
|
363
381
|
});
|
|
364
|
-
return;
|
|
382
|
+
return false;
|
|
365
383
|
}
|
|
366
|
-
const message = `${checkName}:
|
|
367
|
-
|
|
384
|
+
const message = `${checkName}: registered and structurally valid; ` +
|
|
385
|
+
"Codex runtime trust is user-controlled and must be reviewed with `/hooks`";
|
|
386
|
+
sink.success(message);
|
|
368
387
|
addFinding({
|
|
369
|
-
severity: "
|
|
388
|
+
severity: "skip",
|
|
370
389
|
message,
|
|
371
390
|
check: checkName,
|
|
372
|
-
status:
|
|
391
|
+
status: "registered",
|
|
373
392
|
registrations: result.registrations,
|
|
374
|
-
|
|
393
|
+
trust_status: "not-verifiable",
|
|
394
|
+
trust_review: "Open `/hooks` in Codex and review the project hook commands.",
|
|
375
395
|
});
|
|
396
|
+
return true;
|
|
376
397
|
}
|
|
377
398
|
catch (cause) {
|
|
378
399
|
const message = `${checkName}: probe failed -- ${String(cause)}`;
|
|
379
400
|
sink.warn(message);
|
|
380
401
|
addFinding({ severity: "warning", message, check: checkName, suggestion: "deft update" });
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
export function runAgentHooksLiveProbeCheck(projectRoot, sink, addFinding, seams) {
|
|
406
|
+
const checkName = "agent-hooks-registration";
|
|
407
|
+
const liveCheckName = "agent-hooks-live-probe";
|
|
408
|
+
try {
|
|
409
|
+
const result = (seams.evaluateAgentHooks ?? evaluateAgentHooks)(projectRoot);
|
|
410
|
+
const liveResult = (seams.probeAgentHooksLive ?? probeAgentHooksLive)(projectRoot);
|
|
411
|
+
if (liveResult.code !== 0) {
|
|
412
|
+
const message = `${liveCheckName}: ${liveResult.message.replace(/\s+/g, " ").trim()}`;
|
|
413
|
+
sink.warn(message);
|
|
414
|
+
addFinding({
|
|
415
|
+
severity: "warning",
|
|
416
|
+
message,
|
|
417
|
+
check: liveCheckName,
|
|
418
|
+
status: liveResult.code === 2 ? "unavailable" : "non-functional",
|
|
419
|
+
cases: liveResult.cases,
|
|
420
|
+
suggestion: "npm i -g @deftai/directive@latest && deft update",
|
|
421
|
+
});
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const message = `${checkName}: registered, structurally valid, and live probe passed; ` +
|
|
425
|
+
"Codex runtime trust is user-controlled and must be reviewed with `/hooks`";
|
|
426
|
+
sink.success(message);
|
|
427
|
+
addFinding({
|
|
428
|
+
severity: "skip",
|
|
429
|
+
message,
|
|
430
|
+
check: liveCheckName,
|
|
431
|
+
status: "registered-and-functional",
|
|
432
|
+
registrations: result.registrations,
|
|
433
|
+
trust_status: "not-verifiable",
|
|
434
|
+
trust_review: "Open `/hooks` in Codex and review the project hook commands.",
|
|
435
|
+
live_probe: "passed",
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
catch (cause) {
|
|
439
|
+
const message = `${liveCheckName}: probe failed -- ${String(cause)}`;
|
|
440
|
+
sink.warn(message);
|
|
441
|
+
addFinding({ severity: "warning", message, check: liveCheckName, suggestion: "deft update" });
|
|
381
442
|
}
|
|
382
443
|
}
|
|
383
444
|
function runInstallIntegrityChecks(projectRoot, sink, addFinding, seams) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { OutputSink } from "./output.js";
|
|
2
|
+
import type { Finding, NpmConfigGet, NpmConfigGetResult } from "./types.js";
|
|
3
|
+
/** Seams for the read-only npm registry routing diagnostic. */
|
|
4
|
+
export interface NpmRegistryMirrorSeams {
|
|
5
|
+
readonly runNpmConfigGet?: NpmConfigGet;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Read one npm configuration key without a shell.
|
|
9
|
+
*
|
|
10
|
+
* `npm config get` reads local/user configuration only; it does not resolve a
|
|
11
|
+
* package or contact a registry. Failures are returned explicitly so doctor can
|
|
12
|
+
* skip this advisory without turning a missing npm binary into a hard error.
|
|
13
|
+
*/
|
|
14
|
+
export declare function defaultNpmConfigGet(key: string, cwd: string): NpmConfigGetResult;
|
|
15
|
+
/**
|
|
16
|
+
* Warn when `@deftai/*` resolves through a non-public npm registry.
|
|
17
|
+
*
|
|
18
|
+
* The finding intentionally records only whether the scoped or default key won
|
|
19
|
+
* precedence. It never includes the configured URL, which may contain internal
|
|
20
|
+
* hostnames or credentials.
|
|
21
|
+
*/
|
|
22
|
+
export declare function runNpmRegistryMirrorCheck(projectRoot: string, sink: OutputSink, addFinding: (finding: Finding) => void, seams?: NpmRegistryMirrorSeams): void;
|
|
23
|
+
//# sourceMappingURL=npm-registry.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { NPM_REGISTRY_MIRROR_DOC_URL, PUBLIC_NPM_REGISTRY } from "./constants.js";
|
|
3
|
+
const CHECK_NAME = "npm-registry-mirror";
|
|
4
|
+
const DEFT_SCOPE_REGISTRY_KEY = "@deftai:registry";
|
|
5
|
+
const DEFAULT_REGISTRY_KEY = "registry";
|
|
6
|
+
const UNSET_NPM_VALUES = new Set(["", "null", "undefined"]);
|
|
7
|
+
/**
|
|
8
|
+
* Read one npm configuration key without a shell.
|
|
9
|
+
*
|
|
10
|
+
* `npm config get` reads local/user configuration only; it does not resolve a
|
|
11
|
+
* package or contact a registry. Failures are returned explicitly so doctor can
|
|
12
|
+
* skip this advisory without turning a missing npm binary into a hard error.
|
|
13
|
+
*/
|
|
14
|
+
export function defaultNpmConfigGet(key, cwd) {
|
|
15
|
+
try {
|
|
16
|
+
const proc = spawnSync("npm", ["config", "get", key], {
|
|
17
|
+
cwd,
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
shell: false,
|
|
20
|
+
timeout: 5_000,
|
|
21
|
+
windowsHide: true,
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
ok: proc.status === 0,
|
|
25
|
+
value: typeof proc.stdout === "string" ? proc.stdout.trim() : "",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { ok: false, value: "" };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function configuredValue(value) {
|
|
33
|
+
const trimmed = value.trim();
|
|
34
|
+
return UNSET_NPM_VALUES.has(trimmed.toLowerCase()) ? null : trimmed;
|
|
35
|
+
}
|
|
36
|
+
function effectiveRegistry(runConfigGet, projectRoot) {
|
|
37
|
+
let scoped;
|
|
38
|
+
try {
|
|
39
|
+
scoped = runConfigGet(DEFT_SCOPE_REGISTRY_KEY, projectRoot);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (!scoped.ok) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const scopedValue = configuredValue(scoped.value);
|
|
48
|
+
if (scopedValue !== null) {
|
|
49
|
+
return { source: "scoped", value: scopedValue };
|
|
50
|
+
}
|
|
51
|
+
let fallback;
|
|
52
|
+
try {
|
|
53
|
+
fallback = runConfigGet(DEFAULT_REGISTRY_KEY, projectRoot);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (!fallback.ok) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const fallbackValue = configuredValue(fallback.value);
|
|
62
|
+
return fallbackValue === null ? null : { source: "default", value: fallbackValue };
|
|
63
|
+
}
|
|
64
|
+
function isValidRegistryUrl(value) {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = new URL(value);
|
|
67
|
+
return (parsed.protocol === "https:" || parsed.protocol === "http:") && parsed.hostname !== "";
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function isCanonicalPublicRegistry(value) {
|
|
74
|
+
try {
|
|
75
|
+
const parsed = new URL(value);
|
|
76
|
+
return (parsed.protocol === "https:" &&
|
|
77
|
+
parsed.hostname === "registry.npmjs.org" &&
|
|
78
|
+
parsed.port === "" &&
|
|
79
|
+
(parsed.pathname === "" || parsed.pathname === "/") &&
|
|
80
|
+
parsed.username === "" &&
|
|
81
|
+
parsed.password === "" &&
|
|
82
|
+
parsed.search === "" &&
|
|
83
|
+
parsed.hash === "");
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function skipFinding(addFinding) {
|
|
90
|
+
addFinding({
|
|
91
|
+
severity: "skip",
|
|
92
|
+
message: `${CHECK_NAME}: skip -- npm registry configuration is unavailable or invalid`,
|
|
93
|
+
check: CHECK_NAME,
|
|
94
|
+
status: "skip",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Warn when `@deftai/*` resolves through a non-public npm registry.
|
|
99
|
+
*
|
|
100
|
+
* The finding intentionally records only whether the scoped or default key won
|
|
101
|
+
* precedence. It never includes the configured URL, which may contain internal
|
|
102
|
+
* hostnames or credentials.
|
|
103
|
+
*/
|
|
104
|
+
export function runNpmRegistryMirrorCheck(projectRoot, sink, addFinding, seams = {}) {
|
|
105
|
+
const selected = effectiveRegistry(seams.runNpmConfigGet ?? defaultNpmConfigGet, projectRoot);
|
|
106
|
+
if (selected === null || !isValidRegistryUrl(selected.value)) {
|
|
107
|
+
skipFinding(addFinding);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (isCanonicalPublicRegistry(selected.value)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const message = `${CHECK_NAME}: @deftai packages resolve through a non-public npm registry. ` +
|
|
114
|
+
"Corporate mirrors can return E404/ETARGET or silently serve stale @latest metadata. " +
|
|
115
|
+
`Where organization policy permits, retry with \`npm i -g @deftai/directive@latest --registry=${PUBLIC_NPM_REGISTRY}\` ` +
|
|
116
|
+
`or add \`@deftai:registry=${PUBLIC_NPM_REGISTRY}\` to \`.npmrc\`. ` +
|
|
117
|
+
`Otherwise ask your registry administrator to sync the @deftai packages. See ${NPM_REGISTRY_MIRROR_DOC_URL}.`;
|
|
118
|
+
sink.warn(message);
|
|
119
|
+
addFinding({
|
|
120
|
+
severity: "warning",
|
|
121
|
+
message,
|
|
122
|
+
check: CHECK_NAME,
|
|
123
|
+
status: "non-public",
|
|
124
|
+
registry_source: selected.source,
|
|
125
|
+
suggestion: NPM_REGISTRY_MIRROR_DOC_URL,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=npm-registry.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface NpmViewVersionResult {
|
|
2
|
+
readonly ok: boolean;
|
|
3
|
+
readonly version: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Query the canonical public registry for the latest published Directive
|
|
7
|
+
* version, independent of the consumer's configured corporate mirror.
|
|
8
|
+
*/
|
|
9
|
+
export declare function defaultNpmViewVersion(): NpmViewVersionResult;
|
|
10
|
+
//# sourceMappingURL=npm-view.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { NPM_PACKAGE_NAME, PUBLIC_NPM_REGISTRY } from "./constants.js";
|
|
3
|
+
/**
|
|
4
|
+
* Query the canonical public registry for the latest published Directive
|
|
5
|
+
* version, independent of the consumer's configured corporate mirror.
|
|
6
|
+
*/
|
|
7
|
+
export function defaultNpmViewVersion() {
|
|
8
|
+
try {
|
|
9
|
+
const proc = spawnSync("npm", [
|
|
10
|
+
"view",
|
|
11
|
+
NPM_PACKAGE_NAME,
|
|
12
|
+
"version",
|
|
13
|
+
`--registry=${PUBLIC_NPM_REGISTRY}`,
|
|
14
|
+
"--ignore-scripts",
|
|
15
|
+
], {
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
shell: false,
|
|
18
|
+
timeout: 15_000,
|
|
19
|
+
windowsHide: true,
|
|
20
|
+
});
|
|
21
|
+
if (proc.error !== undefined || proc.status !== 0) {
|
|
22
|
+
return { ok: false, version: "" };
|
|
23
|
+
}
|
|
24
|
+
const version = (typeof proc.stdout === "string" ? proc.stdout : "").trim().split("\n")[0]?.trim() ?? "";
|
|
25
|
+
return { ok: version.length > 0, version };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { ok: false, version: "" };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=npm-view.js.map
|
|
@@ -2,8 +2,9 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { detectCanonicalVendoredManifest, isNpmManaged } from "../init-deposit/migrate.js";
|
|
4
4
|
import { isPublishable } from "../release/version.js";
|
|
5
|
-
import { CANONICAL_UPGRADE_COMMAND,
|
|
5
|
+
import { CANONICAL_UPGRADE_COMMAND, upgradeCommandFor, VENDORED_NPM_DEPOSIT_UPGRADE_COMMAND, } from "./constants.js";
|
|
6
6
|
import { locateManifest, parseInstallManifest } from "./manifest.js";
|
|
7
|
+
import { defaultNpmViewVersion } from "./npm-view.js";
|
|
7
8
|
import { readTextSafe, resolveDefaultFrameworkRoot } from "./paths.js";
|
|
8
9
|
import { evaluateReleaseAvailability } from "./release-availability.js";
|
|
9
10
|
function isDeftFrameworkRepo(projectRoot, readText = readTextSafe) {
|
|
@@ -40,14 +41,6 @@ function parseRemoteSha(stdout) {
|
|
|
40
41
|
const firstLine = stdout.split("\n").find((ln) => ln.trim()) ?? "";
|
|
41
42
|
return firstLine.trim().split(/\s+/)[0] ?? "";
|
|
42
43
|
}
|
|
43
|
-
function defaultNpmViewVersion() {
|
|
44
|
-
const proc = spawnSync("npm", ["view", NPM_PACKAGE_NAME, "version"], {
|
|
45
|
-
encoding: "utf8",
|
|
46
|
-
timeout: 15_000,
|
|
47
|
-
});
|
|
48
|
-
const version = (proc.stdout ?? "").trim().split("\n")[0]?.trim() ?? "";
|
|
49
|
-
return { ok: proc.status === 0 && version.length > 0, version };
|
|
50
|
-
}
|
|
51
44
|
function emitReleaseAvailable(checkName, installedVersion, latestVersion, ref, upgradeCommand, sink, addFinding) {
|
|
52
45
|
const msg = `Newer framework release available (installed v${installedVersion}; ` +
|
|
53
46
|
`latest v${latestVersion} from npm registry). ` +
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type CheckSeams } from "./checks.js";
|
|
2
|
+
import { type NpmRegistryMirrorSeams } from "./npm-registry.js";
|
|
2
3
|
import type { OutputSink } from "./output.js";
|
|
3
4
|
import type { Finding } from "./types.js";
|
|
4
|
-
export interface LocalSignpostSeams extends CheckSeams {
|
|
5
|
+
export interface LocalSignpostSeams extends CheckSeams, NpmRegistryMirrorSeams {
|
|
5
6
|
readonly runningInsideDeftRepo?: (root: string) => boolean;
|
|
6
7
|
}
|
|
7
8
|
/** Lightweight, local-only signpost probes for the throttle-skip path (#1997). */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { checkCanonicalVendoredNpmSignpost, checkLegacyLayout } from "./checks.js";
|
|
2
|
+
import { runNpmRegistryMirrorCheck } from "./npm-registry.js";
|
|
2
3
|
import { runningInsideDeftRepo } from "./paths.js";
|
|
3
4
|
/** Lightweight, local-only signpost probes for the throttle-skip path (#1997). */
|
|
4
5
|
export function runLocalSignpostChecks(projectRoot, sink, addFinding, seams = {}) {
|
|
@@ -6,6 +7,7 @@ export function runLocalSignpostChecks(projectRoot, sink, addFinding, seams = {}
|
|
|
6
7
|
if (insideDeft) {
|
|
7
8
|
return;
|
|
8
9
|
}
|
|
10
|
+
runNpmRegistryMirrorCheck(projectRoot, sink, addFinding, seams);
|
|
9
11
|
for (const result of [
|
|
10
12
|
checkLegacyLayout(projectRoot, seams),
|
|
11
13
|
checkCanonicalVendoredNpmSignpost(projectRoot, seams),
|