@deftai/directive-core 0.78.0 → 0.79.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/empty-populate.d.ts +44 -0
- package/dist/cache/empty-populate.js +117 -0
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/check-updates/index.d.ts +10 -0
- package/dist/check-updates/index.js +81 -1
- package/dist/doctor/main.js +13 -2
- package/dist/doctor/payload-staleness.js +7 -1
- package/dist/hooks/dispatcher.d.ts +1 -1
- package/dist/hooks/dispatcher.js +19 -17
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +10 -0
- package/dist/init-deposit/hygiene.js +1 -0
- package/dist/init-deposit/init-deposit.js +2 -0
- package/dist/init-deposit/prettierignore.d.ts +21 -0
- package/dist/init-deposit/prettierignore.js +83 -0
- package/dist/init-deposit/refresh.d.ts +3 -1
- package/dist/init-deposit/refresh.js +29 -40
- package/dist/init-deposit/scaffold.js +6 -12
- package/dist/init-deposit/xbrief-projections.d.ts +18 -0
- package/dist/init-deposit/xbrief-projections.js +117 -0
- package/dist/intake/reconcile-issues.d.ts +10 -0
- package/dist/intake/reconcile-issues.js +115 -2
- package/dist/issue-sync/index.d.ts +2 -0
- package/dist/issue-sync/index.js +2 -0
- package/dist/issue-sync/sync-from-xbrief-cli.d.ts +11 -0
- package/dist/issue-sync/sync-from-xbrief-cli.js +44 -0
- package/dist/issue-sync/sync-from-xbrief.d.ts +46 -0
- package/dist/issue-sync/sync-from-xbrief.js +208 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +5 -0
- package/dist/platform/platform-capabilities.js +9 -0
- package/dist/platform/shell-context.d.ts +30 -0
- package/dist/platform/shell-context.js +111 -0
- package/dist/pr-monitor/cadence.d.ts +6 -0
- package/dist/pr-monitor/cadence.js +13 -0
- package/dist/pr-monitor/index.d.ts +1 -1
- package/dist/pr-monitor/index.js +1 -1
- package/dist/pr-monitor/monitor.d.ts +7 -0
- package/dist/pr-monitor/monitor.js +59 -13
- package/dist/preflight-cache/evaluate.d.ts +3 -8
- package/dist/preflight-cache/evaluate.js +19 -19
- package/dist/release/constants.d.ts +2 -0
- package/dist/release/constants.js +8 -1
- package/dist/release/flags.js +26 -0
- package/dist/release/issue-state-fetch.d.ts +27 -0
- package/dist/release/issue-state-fetch.js +146 -0
- package/dist/release/main.js +1 -0
- package/dist/release/native-steps.js +6 -4
- package/dist/release/pipeline.js +15 -5
- package/dist/release/preflight.d.ts +6 -2
- package/dist/release/preflight.js +19 -5
- package/dist/release/types.d.ts +3 -1
- package/dist/release-e2e/constants.js +6 -6
- package/dist/release-e2e/flags.d.ts +2 -0
- package/dist/release-e2e/flags.js +8 -4
- package/dist/release-e2e/main.js +15 -9
- package/dist/release-e2e/types.d.ts +4 -2
- package/dist/resolution/classify.d.ts +1 -1
- package/dist/resolution/classify.js +36 -11
- package/dist/scope/project-definition-sync.js +2 -150
- package/dist/scope/registry-artifact-sync.d.ts +3 -0
- package/dist/scope/registry-artifact-sync.js +159 -0
- package/dist/scope/specification-sync.d.ts +3 -0
- package/dist/scope/specification-sync.js +16 -0
- package/dist/scope/transition.js +40 -12
- package/dist/session/cache-recovery.d.ts +19 -0
- package/dist/session/cache-recovery.js +26 -0
- package/dist/session/session-start.d.ts +2 -0
- package/dist/session/session-start.js +14 -3
- package/dist/session/verify-session-ritual.d.ts +1 -0
- package/dist/session/verify-session-ritual.js +1 -0
- package/dist/triage/welcome/default-mode.js +2 -0
- package/dist/triage/welcome/onboard.js +2 -0
- package/dist/ts-check-lane/run-lane.d.ts +5 -0
- package/dist/ts-check-lane/run-lane.js +9 -0
- package/dist/vbrief-reconcile/labels.d.ts +3 -0
- package/dist/vbrief-reconcile/labels.js +14 -0
- package/dist/vbrief-reconcile/main.d.ts +1 -0
- package/dist/vbrief-reconcile/main.js +13 -3
- package/dist/vbrief-reconcile/parity-scenarios.js +18 -4
- package/dist/vbrief-reconcile/repo-guard.d.ts +17 -0
- package/dist/vbrief-reconcile/repo-guard.js +33 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +3 -0
- package/dist/vbrief-reconcile/umbrellas.js +17 -1
- package/dist/verify-env/agent-hooks.js +4 -3
- package/dist/verify-env/verify-hooks-installed.js +5 -5
- package/dist/vitest-runner/coverage-debt-teardown.d.ts +3 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +33 -0
- package/dist/vitest-runner/coverage-debt.d.ts +50 -0
- package/dist/vitest-runner/coverage-debt.js +155 -0
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +12 -0
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +24 -0
- package/dist/xbrief-migrate/detect.js +46 -1
- package/dist/xbrief-migrate/migrate-project.js +19 -4
- package/dist/xbrief-migrate/migration-containment.d.ts +6 -0
- package/dist/xbrief-migrate/migration-containment.js +37 -0
- package/package.json +7 -3
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* empty-populate.ts -- auto-fetch GitHub when the triage cache is empty (#2575).
|
|
3
|
+
*
|
|
4
|
+
* Empty/missing cache is the one case where a live list beats a false-empty
|
|
5
|
+
* "what's next?" answer. Non-empty caches keep the 24h freshness gate unchanged.
|
|
6
|
+
*/
|
|
7
|
+
import type { FetchAllReportImpl } from "./fetch.js";
|
|
8
|
+
export interface EmptyPopulateResult {
|
|
9
|
+
readonly skipped: boolean;
|
|
10
|
+
readonly skipReason: string | null;
|
|
11
|
+
readonly repo: string | null;
|
|
12
|
+
readonly populated: boolean;
|
|
13
|
+
readonly succeeded: number | null;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
}
|
|
16
|
+
export interface EmptyPopulateOptions {
|
|
17
|
+
readonly repo?: string | null;
|
|
18
|
+
readonly cacheRoot?: string;
|
|
19
|
+
readonly source?: string;
|
|
20
|
+
readonly inferRepoFn?: (cwd: string) => string | null;
|
|
21
|
+
readonly fetchFn?: (options: {
|
|
22
|
+
source: string;
|
|
23
|
+
repo: string;
|
|
24
|
+
cacheRoot: string;
|
|
25
|
+
force: boolean;
|
|
26
|
+
}) => FetchAllReportImpl;
|
|
27
|
+
readonly seedFn?: (projectRoot: string) => {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
};
|
|
30
|
+
readonly backfillFn?: (projectRoot: string, repo: string | null) => {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** True when `.deft-cache/github-issue/` has zero cached issue entries. */
|
|
35
|
+
export declare function isTriageCacheEmpty(projectRoot: string, cacheRoot?: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* When the triage cache is empty, mirror `triage:bootstrap` populate steps:
|
|
38
|
+
* fetch-all from GitHub, seed the candidates log, backfill lifecycle accepts.
|
|
39
|
+
* Never auto-accepts new issues beyond existing xBRIEF backfill.
|
|
40
|
+
*/
|
|
41
|
+
export declare function maybeAutoPopulateEmptyCache(projectRoot: string, options?: EmptyPopulateOptions): EmptyPopulateResult;
|
|
42
|
+
/** Idempotent hydrate hook for triage read paths (#2575). */
|
|
43
|
+
export declare function ensureTriageCacheHydrated(projectRoot: string, options?: EmptyPopulateOptions): EmptyPopulateResult;
|
|
44
|
+
//# sourceMappingURL=empty-populate.d.ts.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* empty-populate.ts -- auto-fetch GitHub when the triage cache is empty (#2575).
|
|
3
|
+
*
|
|
4
|
+
* Empty/missing cache is the one case where a live list beats a false-empty
|
|
5
|
+
* "what's next?" answer. Non-empty caches keep the 24h freshness gate unchanged.
|
|
6
|
+
*/
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
import { stepSeedCandidatesLog } from "../triage/bootstrap/gitignore.js";
|
|
9
|
+
import { inferRepoFromGit, stepBackfillAuditLog } from "../triage/bootstrap/index.js";
|
|
10
|
+
import { ENV_TRIAGE_REPO } from "../triage/queue/constants.js";
|
|
11
|
+
import { CACHE_DIR_NAME, CACHE_SOURCE, iterCachedIssues } from "../triage/summary/index.js";
|
|
12
|
+
import { cacheFetchAll } from "./fetch.js";
|
|
13
|
+
/** True when `.deft-cache/github-issue/` has zero cached issue entries. */
|
|
14
|
+
export function isTriageCacheEmpty(projectRoot, cacheRoot) {
|
|
15
|
+
const root = resolve(projectRoot);
|
|
16
|
+
const resolvedCacheRoot = cacheRoot ?? join(root, CACHE_DIR_NAME);
|
|
17
|
+
return iterCachedIssues(resolvedCacheRoot).length === 0;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* When the triage cache is empty, mirror `triage:bootstrap` populate steps:
|
|
21
|
+
* fetch-all from GitHub, seed the candidates log, backfill lifecycle accepts.
|
|
22
|
+
* Never auto-accepts new issues beyond existing xBRIEF backfill.
|
|
23
|
+
*/
|
|
24
|
+
export function maybeAutoPopulateEmptyCache(projectRoot, options = {}) {
|
|
25
|
+
const root = resolve(projectRoot);
|
|
26
|
+
const source = options.source ?? CACHE_SOURCE;
|
|
27
|
+
const cacheRoot = options.cacheRoot ?? join(root, CACHE_DIR_NAME);
|
|
28
|
+
if (!isTriageCacheEmpty(root, cacheRoot)) {
|
|
29
|
+
return {
|
|
30
|
+
skipped: true,
|
|
31
|
+
skipReason: "cache-non-empty",
|
|
32
|
+
repo: null,
|
|
33
|
+
populated: false,
|
|
34
|
+
succeeded: null,
|
|
35
|
+
message: "cache already populated",
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const infer = options.inferRepoFn ?? inferRepoFromGit;
|
|
39
|
+
const envRepo = (process.env[ENV_TRIAGE_REPO] ?? "").trim();
|
|
40
|
+
const repo = options.repo ?? (envRepo.length > 0 ? envRepo : null) ?? infer(root);
|
|
41
|
+
if (repo === null) {
|
|
42
|
+
return {
|
|
43
|
+
skipped: true,
|
|
44
|
+
skipReason: "repo-not-resolved",
|
|
45
|
+
repo: null,
|
|
46
|
+
populated: false,
|
|
47
|
+
succeeded: null,
|
|
48
|
+
message: "could not infer repo for empty-cache auto-populate",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const fetch = options.fetchFn ?? cacheFetchAll;
|
|
52
|
+
let report;
|
|
53
|
+
try {
|
|
54
|
+
report = fetch({ source, repo, cacheRoot, force: true });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
return {
|
|
59
|
+
skipped: true,
|
|
60
|
+
skipReason: "fetch-failed",
|
|
61
|
+
repo,
|
|
62
|
+
populated: false,
|
|
63
|
+
succeeded: null,
|
|
64
|
+
message,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const seed = options.seedFn ?? stepSeedCandidatesLog;
|
|
68
|
+
const seedOutcome = seed(root);
|
|
69
|
+
if (!seedOutcome.ok) {
|
|
70
|
+
return {
|
|
71
|
+
skipped: true,
|
|
72
|
+
skipReason: "seed-failed",
|
|
73
|
+
repo,
|
|
74
|
+
populated: false,
|
|
75
|
+
succeeded: report.issuesWritten,
|
|
76
|
+
message: "candidates log seed failed after fetch-all",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const backfill = options.backfillFn ?? stepBackfillAuditLog;
|
|
80
|
+
try {
|
|
81
|
+
const backfillOutcome = backfill(root, repo);
|
|
82
|
+
if (!backfillOutcome.ok) {
|
|
83
|
+
return {
|
|
84
|
+
skipped: true,
|
|
85
|
+
skipReason: "backfill-failed",
|
|
86
|
+
repo,
|
|
87
|
+
populated: false,
|
|
88
|
+
succeeded: report.issuesWritten,
|
|
89
|
+
message: "audit log backfill failed after fetch-all",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
return {
|
|
96
|
+
skipped: true,
|
|
97
|
+
skipReason: "backfill-failed",
|
|
98
|
+
repo,
|
|
99
|
+
populated: false,
|
|
100
|
+
succeeded: report.issuesWritten,
|
|
101
|
+
message,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
skipped: false,
|
|
106
|
+
skipReason: null,
|
|
107
|
+
repo,
|
|
108
|
+
populated: true,
|
|
109
|
+
succeeded: report.issuesWritten,
|
|
110
|
+
message: `auto-populated empty triage cache from GitHub (${repo})`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** Idempotent hydrate hook for triage read paths (#2575). */
|
|
114
|
+
export function ensureTriageCacheHydrated(projectRoot, options = {}) {
|
|
115
|
+
return maybeAutoPopulateEmptyCache(projectRoot, options);
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=empty-populate.js.map
|
package/dist/cache/index.d.ts
CHANGED
package/dist/cache/index.js
CHANGED
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
export declare const REMOTE_PROBE_DEFAULT_TIMEOUT = 5;
|
|
3
3
|
/** Baked-in canonical upstream (#1320). Never probe consumer origin. */
|
|
4
4
|
export declare const DEFT_UPSTREAM_URL = "https://github.com/deftai/directive.git";
|
|
5
|
+
/** SSH form of the canonical upstream — allowlisted alongside HTTPS (#2601). */
|
|
6
|
+
export declare const DEFT_UPSTREAM_SSH_URL = "git@github.com:deftai/directive.git";
|
|
5
7
|
/** Reject git ls-remote targets that could be parsed as options (#CodeQL). */
|
|
6
8
|
export declare function isSafeGitLsRemoteTarget(url: string): boolean;
|
|
7
9
|
/** Return a trimmed upstream URL safe for `git ls-remote`, or null when rejected. */
|
|
8
10
|
export declare function sanitizeGitLsRemoteTarget(url: string): string | null;
|
|
11
|
+
/** Extract hostname from an https or git@ upstream URL. */
|
|
12
|
+
export declare function extractUpstreamHostname(url: string): string | null;
|
|
13
|
+
/** Reject localhost, link-local, and RFC1918 targets for blind SSRF hardening (#2601). */
|
|
14
|
+
export declare function isPrivateOrLinkLocalUpstreamHost(hostname: string): boolean;
|
|
15
|
+
/** True when a sanitized upstream URL is on the canonical allowlist (#2601). */
|
|
16
|
+
export declare function isAllowlistedSafeUpstreamUrl(safeUrl: string): boolean;
|
|
17
|
+
/** True when a manifest-controlled upstream URL is safe to probe (#2601). */
|
|
18
|
+
export declare function isAllowlistedUpstreamUrl(url: string): boolean;
|
|
9
19
|
export type RemoteProbeStatus = "ok" | "behind" | "skipped" | "no-upstream" | "no-tags" | "error";
|
|
10
20
|
export interface RemoteProbeResult {
|
|
11
21
|
readonly status: RemoteProbeStatus;
|
|
@@ -7,6 +7,9 @@ import { resolveVersion } from "../doctor/paths.js";
|
|
|
7
7
|
export const REMOTE_PROBE_DEFAULT_TIMEOUT = 5.0;
|
|
8
8
|
/** Baked-in canonical upstream (#1320). Never probe consumer origin. */
|
|
9
9
|
export const DEFT_UPSTREAM_URL = "https://github.com/deftai/directive.git";
|
|
10
|
+
/** SSH form of the canonical upstream — allowlisted alongside HTTPS (#2601). */
|
|
11
|
+
export const DEFT_UPSTREAM_SSH_URL = "git@github.com:deftai/directive.git";
|
|
12
|
+
const ALLOWED_UPSTREAM_URLS = new Set([DEFT_UPSTREAM_URL, DEFT_UPSTREAM_SSH_URL]);
|
|
10
13
|
const MANIFEST_UPSTREAM_URL_KEYS = [
|
|
11
14
|
"source_url",
|
|
12
15
|
"url",
|
|
@@ -32,6 +35,83 @@ export function sanitizeGitLsRemoteTarget(url) {
|
|
|
32
35
|
}
|
|
33
36
|
return null;
|
|
34
37
|
}
|
|
38
|
+
/** Extract hostname from an https or git@ upstream URL. */
|
|
39
|
+
export function extractUpstreamHostname(url) {
|
|
40
|
+
const trimmed = url.trim();
|
|
41
|
+
const httpsMatch = /^https?:\/\/([^/:?#]+)/i.exec(trimmed);
|
|
42
|
+
if (httpsMatch?.[1]) {
|
|
43
|
+
return httpsMatch[1].toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
const sshMatch = /^git@([^:/]+):/i.exec(trimmed);
|
|
46
|
+
if (sshMatch?.[1]) {
|
|
47
|
+
return sshMatch[1].toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function parseIpv4Octets(hostname) {
|
|
52
|
+
const parts = hostname.split(".");
|
|
53
|
+
if (parts.length !== 4) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const octets = [];
|
|
57
|
+
for (const part of parts) {
|
|
58
|
+
if (!/^\d+$/.test(part)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const value = Number(part);
|
|
62
|
+
if (value > 255) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
octets.push(value);
|
|
66
|
+
}
|
|
67
|
+
return octets;
|
|
68
|
+
}
|
|
69
|
+
/** Reject localhost, link-local, and RFC1918 targets for blind SSRF hardening (#2601). */
|
|
70
|
+
export function isPrivateOrLinkLocalUpstreamHost(hostname) {
|
|
71
|
+
const host = hostname.trim().toLowerCase();
|
|
72
|
+
if (host.length === 0) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (host === "::1" || host === "[::1]") {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
const octets = parseIpv4Octets(host);
|
|
82
|
+
if (octets !== null) {
|
|
83
|
+
const [a, b] = octets;
|
|
84
|
+
if (a === 127)
|
|
85
|
+
return true;
|
|
86
|
+
if (a === 10)
|
|
87
|
+
return true;
|
|
88
|
+
if (a === 172 && b !== undefined && b >= 16 && b <= 31)
|
|
89
|
+
return true;
|
|
90
|
+
if (a === 192 && b === 168)
|
|
91
|
+
return true;
|
|
92
|
+
if (a === 169 && b === 254)
|
|
93
|
+
return true;
|
|
94
|
+
if (a === 0)
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
/** True when a sanitized upstream URL is on the canonical allowlist (#2601). */
|
|
100
|
+
export function isAllowlistedSafeUpstreamUrl(safeUrl) {
|
|
101
|
+
if (!ALLOWED_UPSTREAM_URLS.has(safeUrl)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const hostname = extractUpstreamHostname(safeUrl);
|
|
105
|
+
if (hostname === null || isPrivateOrLinkLocalUpstreamHost(hostname)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
/** True when a manifest-controlled upstream URL is safe to probe (#2601). */
|
|
111
|
+
export function isAllowlistedUpstreamUrl(url) {
|
|
112
|
+
const safe = sanitizeGitLsRemoteTarget(url);
|
|
113
|
+
return safe !== null && isAllowlistedSafeUpstreamUrl(safe);
|
|
114
|
+
}
|
|
35
115
|
function normalizePrereleaseForSort(pre) {
|
|
36
116
|
const dot = pre.indexOf(".");
|
|
37
117
|
if (dot <= 0) {
|
|
@@ -134,7 +214,7 @@ export function resolveUpstreamUrl(projectRoot) {
|
|
|
134
214
|
const value = manifest[key];
|
|
135
215
|
if (typeof value === "string" && value.trim()) {
|
|
136
216
|
const safe = sanitizeGitLsRemoteTarget(value);
|
|
137
|
-
if (safe) {
|
|
217
|
+
if (safe && isAllowlistedSafeUpstreamUrl(safe)) {
|
|
138
218
|
return safe;
|
|
139
219
|
}
|
|
140
220
|
}
|
package/dist/doctor/main.js
CHANGED
|
@@ -349,7 +349,18 @@ export function runAgentHooksHealthCheck(projectRoot, consumerContext, sink, add
|
|
|
349
349
|
try {
|
|
350
350
|
const result = (seams.evaluateAgentHooks ?? evaluateAgentHooks)(projectRoot);
|
|
351
351
|
if (result.code === 0) {
|
|
352
|
-
|
|
352
|
+
const message = `${checkName}: registered and structurally valid; ` +
|
|
353
|
+
"Codex runtime trust is user-controlled and must be reviewed with `/hooks`";
|
|
354
|
+
sink.success(message);
|
|
355
|
+
addFinding({
|
|
356
|
+
severity: "skip",
|
|
357
|
+
message,
|
|
358
|
+
check: checkName,
|
|
359
|
+
status: "registered",
|
|
360
|
+
registrations: result.registrations,
|
|
361
|
+
trust_status: "not-verifiable",
|
|
362
|
+
trust_review: "Open `/hooks` in Codex and review the project hook commands.",
|
|
363
|
+
});
|
|
353
364
|
return;
|
|
354
365
|
}
|
|
355
366
|
const message = `${checkName}: ${result.message.replace(/\s+/g, " ").trim()}`;
|
|
@@ -358,7 +369,7 @@ export function runAgentHooksHealthCheck(projectRoot, consumerContext, sink, add
|
|
|
358
369
|
severity: "warning",
|
|
359
370
|
message,
|
|
360
371
|
check: checkName,
|
|
361
|
-
status: result.code === 2 ? "unavailable" : "
|
|
372
|
+
status: result.code === 2 ? "unavailable" : "incomplete",
|
|
362
373
|
registrations: result.registrations,
|
|
363
374
|
suggestion: "deft update",
|
|
364
375
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { isPublishable } from "../release/version.js";
|
|
4
5
|
import { CANONICAL_UPGRADE_COMMAND, NPM_PACKAGE_NAME, upgradeCommandFor, VENDORED_NPM_DEPOSIT_UPGRADE_COMMAND, } from "./constants.js";
|
|
5
6
|
import { locateManifest, parseInstallManifest } from "./manifest.js";
|
|
6
7
|
import { readTextSafe, resolveDefaultFrameworkRoot } from "./paths.js";
|
|
@@ -203,7 +204,12 @@ export function runPayloadStalenessCheck(projectRoot, sink, addFinding, seams =
|
|
|
203
204
|
});
|
|
204
205
|
return;
|
|
205
206
|
}
|
|
206
|
-
const
|
|
207
|
+
const normalizedRef = ref.trim().replace(/^refs\/tags\//, "");
|
|
208
|
+
const normalizedTag = tag.trim().replace(/^refs\/tags\//, "");
|
|
209
|
+
// Branch pins (ref not a publishable tag) must not fall through to npm via a stale tag (#2538).
|
|
210
|
+
const installedCandidate = isPublishable(normalizedRef)
|
|
211
|
+
? normalizedTag || normalizedRef
|
|
212
|
+
: normalizedRef;
|
|
207
213
|
const applicability = evaluateReleaseAvailability(installedCandidate, null);
|
|
208
214
|
if (applicability.status === "not-applicable") {
|
|
209
215
|
if (remoteSha && installedSha === remoteSha) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type VerifyResult } from "../session/verify-session-ritual.js";
|
|
2
2
|
import { type ActiveScopeInspection } from "./scope.js";
|
|
3
3
|
export { DIRECT_WRITE_TOOL_NAMES, isDirectWriteTool } from "./tools.js";
|
|
4
|
-
export declare const HOOK_HOSTS: readonly ["claude", "grok", "cursor"];
|
|
4
|
+
export declare const HOOK_HOSTS: readonly ["claude", "grok", "cursor", "codex"];
|
|
5
5
|
export type HookHost = (typeof HOOK_HOSTS)[number];
|
|
6
6
|
export declare const HOOK_EVENTS: readonly ["session.start", "tool.before"];
|
|
7
7
|
export type HookEvent = (typeof HOOK_EVENTS)[number];
|
package/dist/hooks/dispatcher.js
CHANGED
|
@@ -4,7 +4,7 @@ import { inspectSessionRitual } from "../session/verify-session-ritual.js";
|
|
|
4
4
|
import { inspectActiveScope } from "./scope.js";
|
|
5
5
|
import { isDirectWriteTool } from "./tools.js";
|
|
6
6
|
export { DIRECT_WRITE_TOOL_NAMES, isDirectWriteTool } from "./tools.js";
|
|
7
|
-
export const HOOK_HOSTS = ["claude", "grok", "cursor"];
|
|
7
|
+
export const HOOK_HOSTS = ["claude", "grok", "cursor", "codex"];
|
|
8
8
|
export const HOOK_EVENTS = ["session.start", "tool.before"];
|
|
9
9
|
function record(value) {
|
|
10
10
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
@@ -150,22 +150,24 @@ export function decideHook(input, seams = {}) {
|
|
|
150
150
|
export function renderHostDecision(host, decision) {
|
|
151
151
|
if (decision.verdict === "allow")
|
|
152
152
|
return "";
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
153
|
+
switch (host) {
|
|
154
|
+
case "claude":
|
|
155
|
+
case "codex":
|
|
156
|
+
return JSON.stringify({
|
|
157
|
+
hookSpecificOutput: {
|
|
158
|
+
hookEventName: "PreToolUse",
|
|
159
|
+
permissionDecision: "deny",
|
|
160
|
+
permissionDecisionReason: decision.message,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
case "grok":
|
|
164
|
+
return JSON.stringify({ decision: "deny", reason: decision.message });
|
|
165
|
+
case "cursor":
|
|
166
|
+
return JSON.stringify({
|
|
167
|
+
permission: "deny",
|
|
168
|
+
user_message: decision.message,
|
|
169
|
+
agent_message: decision.message,
|
|
170
|
+
});
|
|
161
171
|
}
|
|
162
|
-
if (host === "grok") {
|
|
163
|
-
return JSON.stringify({ decision: "deny", reason: decision.message });
|
|
164
|
-
}
|
|
165
|
-
return JSON.stringify({
|
|
166
|
-
permission: "deny",
|
|
167
|
-
user_message: decision.message,
|
|
168
|
-
agent_message: decision.message,
|
|
169
|
-
});
|
|
170
172
|
}
|
|
171
173
|
//# sourceMappingURL=dispatcher.js.map
|
|
@@ -2,7 +2,7 @@ import type { HookHost } from "../hooks/dispatcher.js";
|
|
|
2
2
|
import type { InitDepositIo } from "./constants.js";
|
|
3
3
|
export declare const DIRECT_WRITE_HOOK_MATCHER: string;
|
|
4
4
|
export declare const DEFT_HOOK_COMMAND_MARKER = "deft hook:dispatch";
|
|
5
|
-
export declare const AGENT_HOOK_PATHS: readonly [".claude/settings.json", ".grok/hooks/deft.json", ".cursor/hooks.json"];
|
|
5
|
+
export declare const AGENT_HOOK_PATHS: readonly [".claude/settings.json", ".grok/hooks/deft.json", ".cursor/hooks.json", ".codex/hooks.json"];
|
|
6
6
|
export type AgentHookPath = (typeof AGENT_HOOK_PATHS)[number];
|
|
7
7
|
export type AgentHookRegistrationStatus = "healthy" | "missing" | "drifted";
|
|
8
8
|
export interface AgentHookInspection {
|
|
@@ -8,6 +8,7 @@ export const AGENT_HOOK_PATHS = [
|
|
|
8
8
|
".claude/settings.json",
|
|
9
9
|
".grok/hooks/deft.json",
|
|
10
10
|
".cursor/hooks.json",
|
|
11
|
+
".codex/hooks.json",
|
|
11
12
|
];
|
|
12
13
|
function object(value) {
|
|
13
14
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
@@ -125,6 +126,10 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
|
|
|
125
126
|
merge: (config, path) => mergeNestedConfig(config, path, "grok"),
|
|
126
127
|
},
|
|
127
128
|
{ path: AGENT_HOOK_PATHS[2], merge: mergeCursorConfig },
|
|
129
|
+
{
|
|
130
|
+
path: AGENT_HOOK_PATHS[3],
|
|
131
|
+
merge: (config, path) => mergeNestedConfig(config, path, "codex"),
|
|
132
|
+
},
|
|
128
133
|
];
|
|
129
134
|
const prepared = definitions.map((definition) => {
|
|
130
135
|
const absolute = join(projectRoot, definition.path);
|
|
@@ -187,6 +192,11 @@ export function inspectAgentHookDeposit(projectRoot) {
|
|
|
187
192
|
valid: (config) => hasNestedRegistration(config, "grok"),
|
|
188
193
|
},
|
|
189
194
|
{ host: "cursor", path: AGENT_HOOK_PATHS[2], valid: hasCursorRegistration },
|
|
195
|
+
{
|
|
196
|
+
host: "codex",
|
|
197
|
+
path: AGENT_HOOK_PATHS[3],
|
|
198
|
+
valid: (config) => hasNestedRegistration(config, "codex"),
|
|
199
|
+
},
|
|
190
200
|
];
|
|
191
201
|
return definitions.map((definition) => {
|
|
192
202
|
const absolute = join(projectRoot, definition.path);
|
|
@@ -24,6 +24,7 @@ export function installerManagedMatchers() {
|
|
|
24
24
|
{ exact: ".claude/settings.json" },
|
|
25
25
|
{ exact: ".grok/hooks/deft.json" },
|
|
26
26
|
{ exact: ".cursor/hooks.json" },
|
|
27
|
+
{ exact: ".codex/hooks.json" },
|
|
27
28
|
{ exact: ".gitattributes" },
|
|
28
29
|
{ exact: ".gitignore" },
|
|
29
30
|
{ exact: "greptile.json" },
|
|
@@ -18,6 +18,7 @@ import { ensureInitGitignoreLines, reconstituteDepositFromContent } from "./giti
|
|
|
18
18
|
import { depositStagePaths, printCommitGuidance } from "./hygiene.js";
|
|
19
19
|
import { buildLegacyRefusalJson, buildLegacyRefusalMessage, detectLegacyLayout, LEGACY_LAYOUT_REFUSED_EXIT_CODE, LegacyLayoutRefusedError, } from "./legacy-detect.js";
|
|
20
20
|
import { printMigrateNudgeIfNeeded } from "./migrate.js";
|
|
21
|
+
import { ensurePrettierIgnoreLines } from "./prettierignore.js";
|
|
21
22
|
import { CANONICAL_INSTALL_ROOT, depositNeutralization, ensureTaskfile, writeAgentsMd, writeAgentsSkills, writeConsumerGitHooks, writeConsumerVbrief, writeInstallManifest, } from "./scaffold.js";
|
|
22
23
|
export function parseInitArgv(canonicalArgv, userArgv = []) {
|
|
23
24
|
const args = [...canonicalArgv, ...userArgv];
|
|
@@ -137,6 +138,7 @@ export async function runInitDeposit(args, io, seams = {}) {
|
|
|
137
138
|
await reconstituteDepositFromContent(contentRoot, deftDir, copyContent);
|
|
138
139
|
await prunePythonArtifactsFromDeposit(deftDir, projectDir, io);
|
|
139
140
|
ensureInitGitignoreLines(projectDir, io);
|
|
141
|
+
ensurePrettierIgnoreLines(projectDir, io);
|
|
140
142
|
const nowIso = seams.nowIso ?? (() => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"));
|
|
141
143
|
const version = readContentVersion(contentRoot, seams.readPackageVersion ?? readCorePackageVersion);
|
|
142
144
|
const manifestFields = {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer `.prettierignore` upkeep for install and npm-managed update (#2534).
|
|
3
|
+
*
|
|
4
|
+
* Managed `.deft/core/` is outside the consumer Prettier format gate. Official
|
|
5
|
+
* install and `directive update` heal the ignore entry idempotently without
|
|
6
|
+
* reformatting the vendored deposit.
|
|
7
|
+
*
|
|
8
|
+
* Refs #2534, #670.
|
|
9
|
+
*/
|
|
10
|
+
import type { InitDepositIo } from "./constants.js";
|
|
11
|
+
/** Directory ignore entry for the hybrid deposit. */
|
|
12
|
+
export declare const PRETTIERIGNORE_DEFT_CORE_LINE = ".deft/core/";
|
|
13
|
+
export interface EnsurePrettierIgnoreResult {
|
|
14
|
+
readonly changed: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Ensure the consumer `.prettierignore` excludes managed `.deft/core/` from
|
|
18
|
+
* Prettier. Idempotent; preserves consumer preamble.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ensurePrettierIgnoreLines(projectDir: string, io: InitDepositIo): EnsurePrettierIgnoreResult;
|
|
21
|
+
//# sourceMappingURL=prettierignore.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consumer `.prettierignore` upkeep for install and npm-managed update (#2534).
|
|
3
|
+
*
|
|
4
|
+
* Managed `.deft/core/` is outside the consumer Prettier format gate. Official
|
|
5
|
+
* install and `directive update` heal the ignore entry idempotently without
|
|
6
|
+
* reformatting the vendored deposit.
|
|
7
|
+
*
|
|
8
|
+
* Refs #2534, #670.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
13
|
+
import { stripGitignoreInlineComment } from "../triage/bootstrap/gitignore.js";
|
|
14
|
+
/** Directory ignore entry for the hybrid deposit. */
|
|
15
|
+
export const PRETTIERIGNORE_DEFT_CORE_LINE = ".deft/core/";
|
|
16
|
+
/** Alternate spellings that already cover the deposit ignore entry. */
|
|
17
|
+
const DEFT_CORE_COVERING_LINES = new Set([".deft/core/", ".deft/core", ".deft/core/**"]);
|
|
18
|
+
const DEFT_FRAMEWORK_PRETTIERIGNORE_HEADER = "# Deft framework: the vendored payload is outside the consumer Prettier gate (#2534).\n";
|
|
19
|
+
function projectionTarget(projectDir) {
|
|
20
|
+
const target = join(projectDir, ".prettierignore");
|
|
21
|
+
assertProjectionContained(projectDir, target);
|
|
22
|
+
return target;
|
|
23
|
+
}
|
|
24
|
+
function prettierIgnoreCoversLine(present, line) {
|
|
25
|
+
if (present.has(line))
|
|
26
|
+
return true;
|
|
27
|
+
if (line === PRETTIERIGNORE_DEFT_CORE_LINE) {
|
|
28
|
+
return [...DEFT_CORE_COVERING_LINES].some((candidate) => present.has(candidate));
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function collectPresentPrettierIgnoreLines(existing) {
|
|
33
|
+
const present = new Set();
|
|
34
|
+
for (const raw of existing.split("\n")) {
|
|
35
|
+
const stripped = stripGitignoreInlineComment(raw);
|
|
36
|
+
if (stripped)
|
|
37
|
+
present.add(stripped);
|
|
38
|
+
}
|
|
39
|
+
return present;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Ensure the consumer `.prettierignore` excludes managed `.deft/core/` from
|
|
43
|
+
* Prettier. Idempotent; preserves consumer preamble.
|
|
44
|
+
*/
|
|
45
|
+
export function ensurePrettierIgnoreLines(projectDir, io) {
|
|
46
|
+
const path = projectionTarget(projectDir);
|
|
47
|
+
let existing = "";
|
|
48
|
+
if (existsSync(path)) {
|
|
49
|
+
try {
|
|
50
|
+
existing = readFileSync(path, { encoding: "utf8" });
|
|
51
|
+
}
|
|
52
|
+
catch (cause) {
|
|
53
|
+
throw new Error(`could not read .prettierignore: ${String(cause)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const present = collectPresentPrettierIgnoreLines(existing);
|
|
57
|
+
const targetLines = [PRETTIERIGNORE_DEFT_CORE_LINE];
|
|
58
|
+
const additions = targetLines.filter((line) => !prettierIgnoreCoversLine(present, line));
|
|
59
|
+
if (additions.length === 0) {
|
|
60
|
+
io.printf(".prettierignore already excludes the managed deft framework deposit — skipping.\n");
|
|
61
|
+
return { changed: false };
|
|
62
|
+
}
|
|
63
|
+
let body = existing;
|
|
64
|
+
if (body !== "" && !body.endsWith("\n")) {
|
|
65
|
+
body += "\n";
|
|
66
|
+
}
|
|
67
|
+
if (body !== "" && !body.endsWith("\n\n")) {
|
|
68
|
+
body += "\n";
|
|
69
|
+
}
|
|
70
|
+
body += DEFT_FRAMEWORK_PRETTIERIGNORE_HEADER;
|
|
71
|
+
for (const add of additions) {
|
|
72
|
+
body += `${add}\n`;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
writeFileSync(path, body, { encoding: "utf8", mode: 0o644 });
|
|
76
|
+
}
|
|
77
|
+
catch (cause) {
|
|
78
|
+
throw new Error(`could not write .prettierignore: ${String(cause)}`);
|
|
79
|
+
}
|
|
80
|
+
io.printf(`.prettierignore updated with Deft framework exclusions: ${additions.join(", ")}\n`);
|
|
81
|
+
return { changed: true };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=prettierignore.js.map
|
|
@@ -12,7 +12,7 @@ import { type ClassifySeams, type EngineInstallRunner, type EngineResolution, ty
|
|
|
12
12
|
import { type GitLsFiles } from "./gitignore.js";
|
|
13
13
|
import { type InitDepositArgs } from "./init-deposit.js";
|
|
14
14
|
import { type LegacyLayoutDetection } from "./legacy-detect.js";
|
|
15
|
-
import { type InitDepositIo } from "./scaffold.js";
|
|
15
|
+
import { type GitHooksSeams, type InitDepositIo } from "./scaffold.js";
|
|
16
16
|
export interface RefreshDepositArgs extends InitDepositArgs {
|
|
17
17
|
readonly upgrade: boolean;
|
|
18
18
|
}
|
|
@@ -45,6 +45,8 @@ export interface RefreshDepositSeams {
|
|
|
45
45
|
* upkeep so the refresh never invokes a destructive `git rm --cached` path.
|
|
46
46
|
*/
|
|
47
47
|
gitLsFiles?: GitLsFiles;
|
|
48
|
+
/** #2530: injected git config seams for {@link writeConsumerGitHooks}. */
|
|
49
|
+
gitHooks?: GitHooksSeams;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* The four states `directive update` classifies an EXISTING install into BEFORE
|