@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
|
@@ -17,7 +17,7 @@ export interface ValueFeedbackConfig {
|
|
|
17
17
|
readonly sessionLine: boolean;
|
|
18
18
|
readonly upstreamPrompt: boolean;
|
|
19
19
|
}
|
|
20
|
-
export type ValueFeedbackSource = "typed" | "org-auto" | "default" | "default-on-error";
|
|
20
|
+
export type ValueFeedbackSource = "typed" | "org-auto" | "install-force-on" | "default" | "default-on-error";
|
|
21
21
|
export interface ValueFeedbackResolved extends ValueFeedbackConfig {
|
|
22
22
|
readonly source: ValueFeedbackSource;
|
|
23
23
|
readonly error: string | null;
|
|
@@ -25,6 +25,8 @@ export interface ValueFeedbackResolved extends ValueFeedbackConfig {
|
|
|
25
25
|
export declare const VALUE_FEEDBACK_CAPABILITY_COST_DISCLOSURE: string;
|
|
26
26
|
/** Validate a `plan.policy.valueFeedback` payload. */
|
|
27
27
|
export declare function validateValueFeedback(value: unknown): string[];
|
|
28
|
+
/** Resolve a typed `valueFeedback` block without org-auto / install-force-on layers. */
|
|
29
|
+
export declare function resolveValueFeedbackFromTypedBlock(raw: unknown): ValueFeedbackResolved;
|
|
28
30
|
export interface ResolveValueFeedbackOptions {
|
|
29
31
|
/** Test seam for origin-org auto-enable resolution (#2376). */
|
|
30
32
|
readonly autoEnable?: OrgAutoEnableOptions;
|
|
@@ -78,4 +80,19 @@ export interface EnableValueFeedbackResult {
|
|
|
78
80
|
}
|
|
79
81
|
/** Persist `valueFeedback.enabled=true` after capability-cost disclosure (#1709). */
|
|
80
82
|
export declare function enableValueFeedback(projectRoot: string, options: EnableValueFeedbackOptions): EnableValueFeedbackResult;
|
|
83
|
+
export interface ClearValueFeedbackOptions {
|
|
84
|
+
readonly actor?: string;
|
|
85
|
+
readonly note?: string;
|
|
86
|
+
}
|
|
87
|
+
export interface ClearValueFeedbackResult {
|
|
88
|
+
readonly exitCode: 0 | 2;
|
|
89
|
+
readonly stdout: string;
|
|
90
|
+
readonly changed: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Remove the typed `valueFeedback` key so trusted-org resolution can return to
|
|
94
|
+
* org-auto (#2822). Does not delete the install-force-on marker — subsequent
|
|
95
|
+
* updates will not re-force against intentional opt-out.
|
|
96
|
+
*/
|
|
97
|
+
export declare function clearValueFeedback(projectRoot: string, options?: ClearValueFeedbackOptions): ClearValueFeedbackResult;
|
|
81
98
|
//# sourceMappingURL=value-feedback.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { atomicWriteProjectDefinition, projectDefinitionMutationLock, } from "../vbrief-build/project-definition-io.js";
|
|
3
|
+
import { valueFeedbackInstallForceOnSource } from "./org-force-on-migration.js";
|
|
3
4
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY, readPlanPolicy } from "./plan-extensions.js";
|
|
4
5
|
import { policyColonInvocation } from "./policy-invocation.js";
|
|
5
6
|
import { appendAuditLog, loadProjectDefinition, projectDefinitionPath } from "./resolve.js";
|
|
@@ -77,7 +78,8 @@ export function validateValueFeedback(value) {
|
|
|
77
78
|
}
|
|
78
79
|
return errors;
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
/** Resolve a typed `valueFeedback` block without org-auto / install-force-on layers. */
|
|
82
|
+
export function resolveValueFeedbackFromTypedBlock(raw) {
|
|
81
83
|
const errors = validateValueFeedback(raw);
|
|
82
84
|
if (errors.length > 0) {
|
|
83
85
|
return defaultResolved("default-on-error", errors[0] ?? "invalid valueFeedback block");
|
|
@@ -130,7 +132,13 @@ export function resolveValueFeedback(projectRoot, options = {}) {
|
|
|
130
132
|
}
|
|
131
133
|
return defaultResolved("default");
|
|
132
134
|
}
|
|
133
|
-
|
|
135
|
+
const raw = policyBlock.valueFeedback;
|
|
136
|
+
const installSource = valueFeedbackInstallForceOnSource(projectRoot, raw);
|
|
137
|
+
if (installSource !== null) {
|
|
138
|
+
const resolved = resolveValueFeedbackFromTypedBlock(raw);
|
|
139
|
+
return { ...resolved, source: installSource };
|
|
140
|
+
}
|
|
141
|
+
return resolveValueFeedbackFromTypedBlock(raw);
|
|
134
142
|
}
|
|
135
143
|
/** Master gate: when `enabled` is false, every downstream path is rejected. */
|
|
136
144
|
export function isValueFeedbackPathAllowed(path, policy) {
|
|
@@ -196,8 +204,10 @@ export function inspectValueFeedback(data, projectRoot, options = {}) {
|
|
|
196
204
|
}
|
|
197
205
|
return fieldFromResolved(defaultResolved("default"));
|
|
198
206
|
}
|
|
199
|
-
const
|
|
200
|
-
|
|
207
|
+
const raw = policyBlock.valueFeedback;
|
|
208
|
+
const installSource = projectRoot !== undefined ? valueFeedbackInstallForceOnSource(projectRoot, raw) : null;
|
|
209
|
+
const resolved = resolveValueFeedbackFromTypedBlock(raw);
|
|
210
|
+
return fieldFromResolved(installSource !== null ? { ...resolved, source: installSource } : resolved);
|
|
201
211
|
}
|
|
202
212
|
/** Persist `valueFeedback.enabled=true` after capability-cost disclosure (#1709). */
|
|
203
213
|
export function enableValueFeedback(projectRoot, options) {
|
|
@@ -259,7 +269,7 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
259
269
|
sessionLine: readPersistedSubFlag("sessionLine"),
|
|
260
270
|
upstreamPrompt: readPersistedSubFlag("upstreamPrompt"),
|
|
261
271
|
};
|
|
262
|
-
const previousNormalized =
|
|
272
|
+
const previousNormalized = resolveValueFeedbackFromTypedBlock(previous);
|
|
263
273
|
const changedFlag = previousNormalized.enabled !== nextBlock.enabled ||
|
|
264
274
|
previousNormalized.emitEvents !== nextBlock.emitEvents ||
|
|
265
275
|
previousNormalized.sessionLine !== nextBlock.sessionLine ||
|
|
@@ -302,4 +312,67 @@ export function enableValueFeedback(projectRoot, options) {
|
|
|
302
312
|
return { exitCode: 2, stdout: `\u274c Config error: ${message}\n`, changed: false };
|
|
303
313
|
}
|
|
304
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Remove the typed `valueFeedback` key so trusted-org resolution can return to
|
|
317
|
+
* org-auto (#2822). Does not delete the install-force-on marker — subsequent
|
|
318
|
+
* updates will not re-force against intentional opt-out.
|
|
319
|
+
*/
|
|
320
|
+
export function clearValueFeedback(projectRoot, options = {}) {
|
|
321
|
+
const path = projectDefinitionPath(projectRoot);
|
|
322
|
+
try {
|
|
323
|
+
const { changed } = projectDefinitionMutationLock(projectRoot, () => {
|
|
324
|
+
const parsed = JSON.parse(readFileSync(path, { encoding: "utf8" }));
|
|
325
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
326
|
+
throw new Error(`PROJECT-DEFINITION at ${path} top-level value is not a JSON object`);
|
|
327
|
+
}
|
|
328
|
+
const data = parsed;
|
|
329
|
+
if (typeof data.plan !== "object" || data.plan === null || Array.isArray(data.plan)) {
|
|
330
|
+
throw new Error("PROJECT-DEFINITION 'plan' is not an object");
|
|
331
|
+
}
|
|
332
|
+
const plan = data.plan;
|
|
333
|
+
migrateLegacyPolicyKey(plan);
|
|
334
|
+
const existingPolicy = plan[PLAN_POLICY_KEY];
|
|
335
|
+
if (typeof existingPolicy !== "object" ||
|
|
336
|
+
existingPolicy === null ||
|
|
337
|
+
Array.isArray(existingPolicy)) {
|
|
338
|
+
return { changed: false };
|
|
339
|
+
}
|
|
340
|
+
const policyBlock = existingPolicy;
|
|
341
|
+
if (!("valueFeedback" in policyBlock)) {
|
|
342
|
+
return { changed: false };
|
|
343
|
+
}
|
|
344
|
+
const previous = policyBlock.valueFeedback;
|
|
345
|
+
delete policyBlock.valueFeedback;
|
|
346
|
+
atomicWriteProjectDefinition(path, data);
|
|
347
|
+
const actor = options.actor ?? policyColonInvocation("clear-value-feedback");
|
|
348
|
+
const note = options.note ?? "";
|
|
349
|
+
const parts = [
|
|
350
|
+
`actor=${actor}`,
|
|
351
|
+
"valueFeedback=cleared",
|
|
352
|
+
`previous=${JSON.stringify(previous ?? null)}`,
|
|
353
|
+
];
|
|
354
|
+
if (note) {
|
|
355
|
+
parts.push(`note=${note.replace(/\n/g, " ").replace(/\r/g, " ")}`);
|
|
356
|
+
}
|
|
357
|
+
appendAuditLog(projectRoot, parts.join(" "));
|
|
358
|
+
return { changed: true };
|
|
359
|
+
});
|
|
360
|
+
const resolved = resolveValueFeedback(projectRoot);
|
|
361
|
+
const lines = [
|
|
362
|
+
`\u2713 ${FIELD_VALUE_FEEDBACK} typed key removed (resolution returns to org-auto/default).`,
|
|
363
|
+
changed
|
|
364
|
+
? " audit: meta/policy-changes.log updated."
|
|
365
|
+
: " no-op: typed key was already absent.",
|
|
366
|
+
formatValueFeedbackStatusLine(resolved),
|
|
367
|
+
];
|
|
368
|
+
return { exitCode: 0, stdout: `${lines.join("\n")}\n`, changed };
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
372
|
+
if (message.includes("PROJECT-DEFINITION not found")) {
|
|
373
|
+
return { exitCode: 2, stdout: `\u274c ${message}\n`, changed: false };
|
|
374
|
+
}
|
|
375
|
+
return { exitCode: 2, stdout: `\u274c Config error: ${message}\n`, changed: false };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
305
378
|
//# sourceMappingURL=value-feedback.js.map
|
|
@@ -14,7 +14,7 @@ import { execFileSync } from "node:child_process";
|
|
|
14
14
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
15
15
|
import { join, relative } from "node:path";
|
|
16
16
|
import { isTriageCacheEmpty, maybeAutoPopulateEmptyCache } from "../cache/empty-populate.js";
|
|
17
|
-
import { probeCacheDrift } from "../cache/fetch.js";
|
|
17
|
+
import { probeCacheDrift, readOpenInventoryStamp, } from "../cache/fetch.js";
|
|
18
18
|
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
19
19
|
import { recoveryHintForStaleFailure } from "../session/cache-recovery.js";
|
|
20
20
|
export { recoveryHintForStaleFailure } from "../session/cache-recovery.js";
|
|
@@ -423,6 +423,17 @@ function evaluateWithContext(projectRoot, options, ctx) {
|
|
|
423
423
|
minMetaPath = p;
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
|
+
// #2826: empty open inventory with a successful fetch stamp is fresh — not
|
|
427
|
+
// the never-fetched case that yields minFetchedAt=null / Infinity age.
|
|
428
|
+
if (minFetchedAt === null && rawCandidatePaths.length === 0 && resolvedRepo !== null) {
|
|
429
|
+
const stamp = readOpenInventoryStamp(cacheRoot, source, resolvedRepo);
|
|
430
|
+
if (stamp !== null) {
|
|
431
|
+
const parsed = new Date(stamp.fetched_at);
|
|
432
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
433
|
+
minFetchedAt = parsed;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
426
437
|
const now = nowFn();
|
|
427
438
|
const ageMs = minFetchedAt !== null ? now.getTime() - minFetchedAt.getTime() : Infinity;
|
|
428
439
|
const ageH = ageMs / (1000 * 3600);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type HeadlessDetectionOptions } from "./headless.js";
|
|
2
|
+
/** D17 first-time consent ask (#2693 / #2822). */
|
|
3
|
+
export declare const PRODUCT_SIGNAL_CONSENT_PROMPT: string;
|
|
4
|
+
export interface ProductSignalConsentEligibilityOptions extends HeadlessDetectionOptions {
|
|
5
|
+
readonly projectRoot: string;
|
|
6
|
+
}
|
|
7
|
+
/** True when an interactive session should surface the D17 consent ask (#2822 AC). */
|
|
8
|
+
export declare function isProductSignalConsentEligible(options: ProductSignalConsentEligibilityOptions): boolean;
|
|
9
|
+
/** Format the D17 consent ask for session start / status surfaces. */
|
|
10
|
+
export declare function formatProductSignalConsentPrompt(): string;
|
|
11
|
+
/** Emit the D17 ask when eligible; headless callers get an empty string (#2693 D16). */
|
|
12
|
+
export declare function maybeFormatProductSignalConsentPrompt(options: ProductSignalConsentEligibilityOptions): string;
|
|
13
|
+
//# sourceMappingURL=consent-prompt.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { resolveProductSignal } from "../policy/product-signal.js";
|
|
2
|
+
import { isProductSignalConsented } from "./consent.js";
|
|
3
|
+
import { isHeadlessSession } from "./headless.js";
|
|
4
|
+
/** D17 first-time consent ask (#2693 / #2822). */
|
|
5
|
+
export const PRODUCT_SIGNAL_CONSENT_PROMPT = "May we collect usage metrics and related session signal from this install to improve Directive? " +
|
|
6
|
+
"This can include a short check-in plus minimized local summaries (value/health and related ledgers). " +
|
|
7
|
+
"Nothing is sent while this path is off or without your consent.\n\n" +
|
|
8
|
+
"If you are unsure, please check with your company before saying no — they are a Directive partner " +
|
|
9
|
+
"and may already expect this signal to be shared.\n\n" +
|
|
10
|
+
"Reply **yes** to consent (`task product-signal:consent -- --grant`), or **no** to decline.";
|
|
11
|
+
/** True when an interactive session should surface the D17 consent ask (#2822 AC). */
|
|
12
|
+
export function isProductSignalConsentEligible(options) {
|
|
13
|
+
if (isHeadlessSession(options)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const policy = resolveProductSignal(options.projectRoot);
|
|
17
|
+
if (!policy.enabled) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return !isProductSignalConsented({ env: options.env });
|
|
21
|
+
}
|
|
22
|
+
/** Format the D17 consent ask for session start / status surfaces. */
|
|
23
|
+
export function formatProductSignalConsentPrompt() {
|
|
24
|
+
return `[deft product-signal] First-time consent eligible:\n${PRODUCT_SIGNAL_CONSENT_PROMPT}\n`;
|
|
25
|
+
}
|
|
26
|
+
/** Emit the D17 ask when eligible; headless callers get an empty string (#2693 D16). */
|
|
27
|
+
export function maybeFormatProductSignalConsentPrompt(options) {
|
|
28
|
+
return isProductSignalConsentEligible(options) ? formatProductSignalConsentPrompt() : "";
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=consent-prompt.js.map
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
3
4
|
import { enableProductSignal, formatProductSignalStatusLine, resolveProductSignal, } from "../policy/product-signal.js";
|
|
4
5
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
5
6
|
import { resolveActorName } from "./actor-name.js";
|
|
6
7
|
import { authorizeProductSignalSink, grantProductSignalConsent, readProductSignalConsent, revokeProductSignalConsent, } from "./consent.js";
|
|
8
|
+
import { maybeFormatProductSignalConsentPrompt } from "./consent-prompt.js";
|
|
7
9
|
import { evaluateProductSignalGates } from "./gates.js";
|
|
8
10
|
import { GitHubPrivateSinkAdapter } from "./github-private-sink-adapter.js";
|
|
9
11
|
import { collectInstallContext } from "./install-context.js";
|
|
@@ -13,6 +15,7 @@ import { bootstrapProductSignalSink } from "./sink-bootstrap.js";
|
|
|
13
15
|
export const PRODUCT_SIGNAL_LAST_SUBMIT_REL = join(".deft-cache", "product-signal-last-submit.json");
|
|
14
16
|
function recordLastSubmit(projectRoot, outcome, url) {
|
|
15
17
|
const path = resolve(projectRoot, PRODUCT_SIGNAL_LAST_SUBMIT_REL);
|
|
18
|
+
assertWriteTargetSafe(projectRoot, path);
|
|
16
19
|
try {
|
|
17
20
|
mkdirSync(join(path, ".."), { recursive: true });
|
|
18
21
|
appendFileSync(path, `${JSON.stringify({
|
|
@@ -127,7 +130,21 @@ export async function submitProductSignal(options) {
|
|
|
127
130
|
const adapter = new GitHubPrivateSinkAdapter({ sinkRepo: policy.sinkRepo });
|
|
128
131
|
const result = await adapter.submit(payload, { gapText: options.gapText });
|
|
129
132
|
if (result.outcome === "submitted") {
|
|
130
|
-
|
|
133
|
+
try {
|
|
134
|
+
recordLastSubmit(root, result.outcome, result.issueUrl ?? null);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (err instanceof ProjectionContainmentError) {
|
|
138
|
+
return {
|
|
139
|
+
outcome: "error-config",
|
|
140
|
+
exitCode: 2,
|
|
141
|
+
message: `${err.message}\n`,
|
|
142
|
+
issueUrl: result.issueUrl,
|
|
143
|
+
payload,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
131
148
|
}
|
|
132
149
|
const urlLine = result.issueUrl ? ` url=${result.issueUrl}` : "";
|
|
133
150
|
return {
|
|
@@ -150,6 +167,10 @@ export function runProductSignalStatus(projectRoot) {
|
|
|
150
167
|
`[deft product-signal] consented=${String(consent !== null)} configuredSink=${configuredSink} consentedSink=${sinkAuth.consentedSink ?? "none"} sinksMatch=${String(sinkAuth.sinksMatch)}`,
|
|
151
168
|
last ? `[deft product-signal] ${last}` : "[deft product-signal] last submit: none",
|
|
152
169
|
];
|
|
170
|
+
const consentPrompt = maybeFormatProductSignalConsentPrompt({ projectRoot: root });
|
|
171
|
+
if (consentPrompt.length > 0) {
|
|
172
|
+
lines.push(consentPrompt.trimEnd());
|
|
173
|
+
}
|
|
153
174
|
return { exitCode: 0, text: `${lines.join("\n")}\n` };
|
|
154
175
|
}
|
|
155
176
|
export function runProductSignalEnable(projectRoot, confirm) {
|
|
@@ -25,7 +25,7 @@ export declare function formatFrameworkCommand(args: readonly string[], options?
|
|
|
25
25
|
surface?: string;
|
|
26
26
|
taskPrefix?: string | null;
|
|
27
27
|
}): string;
|
|
28
|
-
export declare function cmdCoreValidate(argv: readonly string[]): number;
|
|
28
|
+
export declare function cmdCoreValidate(argv: readonly string[], root?: string): number;
|
|
29
29
|
export declare function cmdCoreLint(_argv: readonly string[]): number;
|
|
30
30
|
export declare function cmdCoreTest(_argv: readonly string[]): number;
|
|
31
31
|
export declare function resolveFrameworkRoot(): string;
|
|
@@ -154,7 +154,7 @@ export function formatFrameworkCommand(args, options = {}) {
|
|
|
154
154
|
}
|
|
155
155
|
return [surface, ...parts].join(" ");
|
|
156
156
|
}
|
|
157
|
-
const EXCLUDE_PARTS = new Set([".git", "backup"]);
|
|
157
|
+
const EXCLUDE_PARTS = new Set([".git", "backup", "node_modules", ".deft-scratch"]);
|
|
158
158
|
function collectMarkdownFiles(root) {
|
|
159
159
|
const out = [];
|
|
160
160
|
const walk = (dir, parts) => {
|
|
@@ -189,12 +189,12 @@ function collectMarkdownFiles(root) {
|
|
|
189
189
|
walk(root, []);
|
|
190
190
|
return out.sort();
|
|
191
191
|
}
|
|
192
|
-
export function cmdCoreValidate(argv) {
|
|
192
|
+
export function cmdCoreValidate(argv, root = ".") {
|
|
193
193
|
if (argv.length > 0) {
|
|
194
194
|
process.stderr.write(`error: core:validate does not accept arguments: ${argv.join(" ")}\n`);
|
|
195
195
|
return 2;
|
|
196
196
|
}
|
|
197
|
-
const files = collectMarkdownFiles(
|
|
197
|
+
const files = collectMarkdownFiles(root);
|
|
198
198
|
for (const path of files)
|
|
199
199
|
process.stdout.write(`✓ ${path}\n`);
|
|
200
200
|
process.stdout.write(`✓ All ${files.length} markdown files validated\n`);
|
|
@@ -271,7 +271,7 @@ function runBuildDistArgv(argv) {
|
|
|
271
271
|
return result.status ?? 1;
|
|
272
272
|
}
|
|
273
273
|
const TS_INLINE = {
|
|
274
|
-
"framework_commands:_cmd_core_validate": (argv) => cmdCoreValidate(argv),
|
|
274
|
+
"framework_commands:_cmd_core_validate": (argv, cwd) => cmdCoreValidate(argv, cwd),
|
|
275
275
|
"doctor:cmd_doctor": (argv) => cmdDoctor(argv),
|
|
276
276
|
"build_dist:main": (argv) => runBuildDistArgv(argv),
|
|
277
277
|
};
|
|
@@ -349,7 +349,7 @@ function invokeEntrypoint(entrypoint, argv, cwd, frameworkRoot, noArgv, capture)
|
|
|
349
349
|
};
|
|
350
350
|
let code;
|
|
351
351
|
try {
|
|
352
|
-
code = inline(argv);
|
|
352
|
+
code = inline(argv, cwd);
|
|
353
353
|
}
|
|
354
354
|
finally {
|
|
355
355
|
process.stdout.write = prevOut;
|
|
@@ -357,7 +357,7 @@ function invokeEntrypoint(entrypoint, argv, cwd, frameworkRoot, noArgv, capture)
|
|
|
357
357
|
}
|
|
358
358
|
return { code, stdout: chunks.out, stderr: chunks.err };
|
|
359
359
|
}
|
|
360
|
-
return { code: inline(argv), stdout: "", stderr: "" };
|
|
360
|
+
return { code: inline(argv, cwd), stdout: "", stderr: "" };
|
|
361
361
|
}
|
|
362
362
|
const verb = ENTRYPOINT_VERB[entrypoint];
|
|
363
363
|
if (verb) {
|
package/dist/render/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * as projectRender from "./project-render.js";
|
|
|
8
8
|
export { acknowledgeProjectDefinitionStaleness, buildStalenessAcknowledgement, computeStalenessFlags, flagStaleNarratives, main as projectRenderMain, parseStalenessReview, renderProjectDefinition, scanLifecycleFolders, unacknowledgedCompletedItems, } from "./project-render.js";
|
|
9
9
|
export * as roadmapRender from "./roadmap-render.js";
|
|
10
10
|
export { checkDrift, generateRoadmapContent, main as roadmapRenderMain, renderRoadmap, renderRoadmapToBuffer, } from "./roadmap-render.js";
|
|
11
|
+
export { main as ruleMapMain } from "./rule-map.js";
|
|
11
12
|
export { aggregateScopeSection, buildScopeOutlookSection } from "./scope-outlook.js";
|
|
12
13
|
export * as specRender from "./spec-render.js";
|
|
13
14
|
export { main as specRenderMain, parseIncludeScopesFlag, renderSpec } from "./spec-render.js";
|
package/dist/render/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * as projectRender from "./project-render.js";
|
|
|
8
8
|
export { acknowledgeProjectDefinitionStaleness, buildStalenessAcknowledgement, computeStalenessFlags, flagStaleNarratives, main as projectRenderMain, parseStalenessReview, renderProjectDefinition, scanLifecycleFolders, unacknowledgedCompletedItems, } from "./project-render.js";
|
|
9
9
|
export * as roadmapRender from "./roadmap-render.js";
|
|
10
10
|
export { checkDrift, generateRoadmapContent, main as roadmapRenderMain, renderRoadmap, renderRoadmapToBuffer, } from "./roadmap-render.js";
|
|
11
|
+
export { main as ruleMapMain } from "./rule-map.js";
|
|
11
12
|
export { aggregateScopeSection, buildScopeOutlookSection } from "./scope-outlook.js";
|
|
12
13
|
export * as specRender from "./spec-render.js";
|
|
13
14
|
export { main as specRenderMain, parseIncludeScopesFlag, renderSpec } from "./spec-render.js";
|
|
@@ -3,7 +3,11 @@ export declare function renderRoadmapToBuffer(pendingDir: string, completedDir?:
|
|
|
3
3
|
/** @deprecated Prefer ``renderRoadmapToBuffer`` — kept for existing imports and parity harnesses. */
|
|
4
4
|
export declare function generateRoadmapContent(pendingDir: string, completedDir?: string): string;
|
|
5
5
|
export type RenderRoadmapResult = readonly [boolean, string];
|
|
6
|
-
export
|
|
6
|
+
export type RenderRoadmapOptions = {
|
|
7
|
+
completedDir?: string;
|
|
8
|
+
projectRoot?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function renderRoadmap(pendingDir: string, outPath: string, completedDirOrOptions?: string | RenderRoadmapOptions): RenderRoadmapResult;
|
|
7
11
|
export declare function checkDrift(pendingDir: string, roadmapPath: string, completedDir?: string): RenderRoadmapResult;
|
|
8
12
|
/** CLI entry (mirrors ``scripts/roadmap_render.main``). */
|
|
9
13
|
export declare function main(argv: readonly string[]): number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { assertWriteTargetSafe } from "../fs/projection-containment.js";
|
|
3
4
|
import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
4
5
|
import { MIGRATOR_METADATA_KEY, ROADMAP_BANNER } from "./constants.js";
|
|
5
6
|
import { phaseSortKey } from "./text-utils.js";
|
|
@@ -351,9 +352,22 @@ export function renderRoadmapToBuffer(pendingDir, completedDir) {
|
|
|
351
352
|
export function generateRoadmapContent(pendingDir, completedDir) {
|
|
352
353
|
return renderRoadmapToBuffer(pendingDir, completedDir);
|
|
353
354
|
}
|
|
354
|
-
export function renderRoadmap(pendingDir, outPath,
|
|
355
|
+
export function renderRoadmap(pendingDir, outPath, completedDirOrOptions) {
|
|
356
|
+
let completedDir;
|
|
357
|
+
let projectRoot;
|
|
358
|
+
if (typeof completedDirOrOptions === "string") {
|
|
359
|
+
completedDir = completedDirOrOptions;
|
|
360
|
+
}
|
|
361
|
+
else if (completedDirOrOptions !== undefined) {
|
|
362
|
+
completedDir = completedDirOrOptions.completedDir;
|
|
363
|
+
projectRoot = completedDirOrOptions.projectRoot;
|
|
364
|
+
}
|
|
355
365
|
try {
|
|
356
366
|
const content = renderRoadmapToBuffer(pendingDir, completedDir);
|
|
367
|
+
// Trust boundary is the project root — never dirname(outPath), which follows a
|
|
368
|
+
// diverted parent symlink and would make containment pass outside the checkout.
|
|
369
|
+
const projectDir = projectRoot !== undefined ? resolve(projectRoot) : resolve(pendingDir, "..", "..");
|
|
370
|
+
assertWriteTargetSafe(projectDir, resolve(outPath));
|
|
357
371
|
writeFileSync(outPath, content, "utf8");
|
|
358
372
|
return [true, `✓ Rendered ROADMAP.md to ${outPath}`];
|
|
359
373
|
}
|
|
@@ -415,7 +429,11 @@ export function main(argv) {
|
|
|
415
429
|
process.stdout.write(`${msg}\n`);
|
|
416
430
|
return ok ? 0 : 1;
|
|
417
431
|
}
|
|
418
|
-
|
|
432
|
+
// When --project-root is set, use it; otherwise renderRoadmap derives from pendingDir
|
|
433
|
+
// (…/xbrief|vbrief/pending → project root). Never use dirname(outPath).
|
|
434
|
+
const [ok, msg] = renderRoadmap(pendingDir, outPath, {
|
|
435
|
+
projectRoot: projectRoot !== undefined ? resolve(projectRoot) : undefined,
|
|
436
|
+
});
|
|
419
437
|
process.stdout.write(`${msg}\n`);
|
|
420
438
|
return ok ? 0 : 1;
|
|
421
439
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const TEMPLATE = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Directive \u2014 Rule Map</title>\n<style>\n :root{\n --bg:#0f1115; --panel:#171a21; --panel2:#1d222b; --line:#2a303b;\n --ink:#e7ebf0; --dim:#96a0ad; --dim2:#6b7482; --accent:#6ea8fe; --accent2:#8be0c0;\n --must:#ff6b6b; --mustnot:#c0392b; --should:#f0b429; --shouldnot:#9a7b12; --may:#7f8a99;\n --radius:12px;\n }\n *{box-sizing:border-box}\n html,body{margin:0;height:100%}\n body{background:var(--bg);color:var(--ink);\n font:15px/1.5 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif}\n code,.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}\n a{color:var(--accent);text-decoration:none}\n .wrap{max-width:1120px;margin:0 auto;padding:22px 24px 80px}\n header.top{display:flex;align-items:center;gap:18px;flex-wrap:wrap;\n border-bottom:1px solid var(--line);padding-bottom:16px;margin-bottom:20px}\n .brand{font-size:20px;font-weight:700;letter-spacing:.2px}\n .brand small{display:block;font-size:12px;color:var(--dim);font-weight:400;margin-top:2px}\n nav.spines{display:flex;gap:6px;margin-left:auto}\n nav.spines button{background:var(--panel);color:var(--dim);border:1px solid var(--line);\n padding:7px 14px;border-radius:999px;cursor:pointer;font-size:13px}\n nav.spines button.active{background:var(--accent);color:#0b0d10;border-color:var(--accent);font-weight:600}\n .search{width:100%;margin:0 0 18px}\n .search input{width:100%;background:var(--panel);border:1px solid var(--line);color:var(--ink);\n padding:11px 14px;border-radius:10px;font-size:14px}\n .search input::placeholder{color:var(--dim2)}\n .crumbs{display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:var(--dim);font-size:13px;margin-bottom:16px}\n .crumbs a{cursor:pointer}\n .crumbs .sep{color:var(--dim2)}\n .crumbs .here{color:var(--ink)}\n .lead{color:var(--dim);margin:-4px 0 20px;max-width:760px}\n .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px}\n .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);\n padding:15px 16px;cursor:pointer;transition:.12s;display:flex;flex-direction:column;gap:7px;min-height:96px}\n .card:hover{border-color:var(--accent);background:var(--panel2);transform:translateY(-1px)}\n .card .name{font-weight:600;font-size:15px;display:flex;align-items:center;gap:8px}\n .card .name .ic{color:var(--dim2);font-size:13px}\n .card .desc{color:var(--dim);font-size:13px;line-height:1.45}\n .card .meta{margin-top:auto;display:flex;align-items:center;gap:10px;color:var(--dim2);font-size:12px}\n .rows{display:flex;flex-direction:column;gap:8px}\n .row{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 14px;\n cursor:pointer;transition:.12s;display:flex;align-items:center;gap:12px}\n .row:hover{border-color:var(--accent);background:var(--panel2)}\n .row .rname{font-weight:600;min-width:0}\n .row .rname .sub{font-weight:400;color:var(--dim);font-size:13px;display:block;margin-top:2px;\n overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n .row .rt{margin-left:auto;display:flex;gap:5px;flex-shrink:0}\n .row .chev{color:var(--dim2);flex-shrink:0}\n .tier{font-size:11px;padding:2px 7px;border-radius:999px;font-weight:600;white-space:nowrap;\n color:#0b0d10;font-family:ui-monospace,monospace}\n .t-MUST{background:var(--must)} .t-MUST-NOT{background:var(--mustnot);color:#fff}\n .t-SHOULD{background:var(--should)} .t-SHOULD-NOT{background:var(--shouldnot);color:#fff}\n .t-MAY{background:var(--may);color:#fff}\n .bar{display:flex;height:6px;border-radius:999px;overflow:hidden;background:var(--panel2);flex:1;max-width:120px}\n .bar span{display:block;height:100%}\n .detail{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:22px 24px}\n .detail h2{margin:0 0 4px;font-size:22px}\n .detail .kind{color:var(--dim2);font-size:12px;text-transform:uppercase;letter-spacing:.08em}\n .detail .summary{color:var(--dim);margin:12px 0 18px;max-width:720px}\n .sectionlist{display:flex;flex-wrap:wrap;gap:7px;margin:6px 0 4px}\n .sectionlist .s{background:var(--panel2);border:1px solid var(--line);border-radius:7px;\n padding:5px 10px;font-size:12.5px;color:var(--ink)}\n .srcbox{margin-top:20px;border-top:1px solid var(--line);padding-top:16px}\n .srcbox .path{font-size:12.5px;color:var(--dim);word-break:break-all;margin-bottom:10px}\n .btns{display:flex;gap:8px;flex-wrap:wrap}\n button.act{background:var(--accent);color:#0b0d10;border:none;padding:8px 14px;border-radius:8px;\n cursor:pointer;font-size:13px;font-weight:600}\n button.act.ghost{background:transparent;color:var(--accent);border:1px solid var(--line)}\n button.act:hover{filter:brightness(1.08)}\n h3.grouphdr{font-size:13px;text-transform:uppercase;letter-spacing:.08em;color:var(--dim2);margin:26px 0 10px}\n .legend{display:flex;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--dim);margin:2px 0 18px}\n .toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);background:var(--panel2);\n border:1px solid var(--line);color:var(--ink);padding:11px 18px;border-radius:10px;font-size:13px;\n opacity:0;transition:.2s;pointer-events:none;box-shadow:0 8px 30px rgba(0,0,0,.4);z-index:50}\n .toast.show{opacity:1}\n .toast.err{border-color:var(--must)}\n .flow{display:flex;flex-wrap:wrap;align-items:stretch;gap:0}\n .flow .node{background:var(--panel2);border:1px solid var(--line);border-radius:9px;padding:10px 14px;text-align:center;min-width:110px}\n .flow .node .st{font-weight:600}\n .flow .node .via{font-size:11px;color:var(--dim2);margin-top:3px;font-family:ui-monospace,monospace}\n .flow .arw{align-self:center;color:var(--dim2);padding:0 8px;font-size:18px}\n .ladder{display:flex;flex-direction:column;gap:8px;margin:6px 0 20px}\n .ladder .lv{display:flex;gap:12px;align-items:baseline;background:var(--panel);border:1px solid var(--line);border-radius:9px;padding:11px 14px}\n .ladder .lv b{min-width:190px}\n .ladder .lv .d{color:var(--dim);font-size:13px}\n .pill{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--dim);\n background:var(--panel2);border:1px solid var(--line);padding:4px 9px;border-radius:999px;margin:0 6px 6px 0}\n .muted{color:var(--dim2);font-size:12px}\n .empty{color:var(--dim);padding:30px;text-align:center}\n .foot{margin-top:34px;color:var(--dim2);font-size:12px;border-top:1px solid var(--line);padding-top:14px}\n /* source viewer */\n .src{margin-top:16px;background:#0b0d12;border:1px solid var(--line);border-radius:10px;padding:16px 18px;\n max-height:60vh;overflow:auto}\n .src h1,.src h2,.src h3{color:var(--ink);margin:14px 0 6px;line-height:1.3}\n .src h1{font-size:19px} .src h2{font-size:16px} .src h3{font-size:14px}\n .src p{margin:8px 0;color:#c7cfda}\n .src ul{margin:8px 0;padding-left:22px;color:#c7cfda}\n .src li{margin:3px 0}\n .src code{background:#161b24;border:1px solid var(--line);border-radius:5px;padding:1px 5px;font-size:12.5px;color:var(--accent2)}\n .src pre{background:#161b24;border:1px solid var(--line);border-radius:8px;padding:12px 14px;overflow:auto}\n .src pre code{background:none;border:none;padding:0;color:#c7cfda}\n .src blockquote{border-left:3px solid var(--line);margin:8px 0;padding:2px 12px;color:var(--dim)}\n .src .trunc{color:var(--dim2);font-size:12px;margin-top:10px}\n</style>\n</head>\n<body>\n<div class=\"wrap\">\n <header class=\"top\">\n <div class=\"brand\">Directive — Rule Map<small id=\"sub\">maintainer map</small></div>\n <nav class=\"spines\">\n <button data-spine=\"rules\">Rules</button>\n <button data-spine=\"tasks\">Tasks</button>\n <button data-spine=\"lifecycle\">Lifecycle</button>\n </nav>\n </header>\n\n <div class=\"search\"><input id=\"q\" placeholder=\"Search groupings, rules, tasks\u2026 (Esc to clear)\"></div>\n <div class=\"crumbs\" id=\"crumbs\"></div>\n <div id=\"view\"></div>\n <div class=\"foot\" id=\"foot\"></div>\n</div>\n<div class=\"toast\" id=\"toast\"></div>\n\n<script>window.DIRECTIVE_DATA = /*__DATA__*/ null;</script>\n<script>\nlet DATA=window.DIRECTIVE_DATA, spine=\"rules\", stack=[];\nconst el=(t,c,txt)=>{const e=document.createElement(t);if(c)e.className=c;if(txt!=null)e.textContent=txt;return e;};\nconst tierClass=t=>\"t-\"+t.replace(/\\s|_/g,\"-\");\nconst TIER_ORDER=[\"MUST\",\"MUST NOT\",\"SHOULD\",\"SHOULD NOT\",\"MAY\"];\nconst TIER_COLOR={\"MUST\":\"var(--must)\",\"MUST NOT\":\"var(--mustnot)\",\"SHOULD\":\"var(--should)\",\"SHOULD NOT\":\"var(--shouldnot)\",\"MAY\":\"var(--may)\"};\nconst MARK2TIER={\"!\":\"MUST\",\"\u2297\":\"MUST NOT\",\"~\":\"SHOULD\",\"\u2249\":\"SHOULD NOT\",\"?\":\"MAY\"};\n\nfunction toast(msg,err){const t=document.getElementById('toast');t.textContent=msg;\n t.className='toast show'+(err?' err':'');setTimeout(()=>t.className='toast',1800);}\nfunction copyPath(p){\n const ok=()=>toast(\"Path copied\");\n const fail=()=>toast(\"Copy failed\",true);\n const fallback=()=>{\n try{\n const ta=document.createElement(\"textarea\");\n ta.value=p;ta.setAttribute(\"readonly\",\"\");\n ta.style.position=\"fixed\";ta.style.left=\"-9999px\";\n document.body.appendChild(ta);ta.select();\n const done=document.execCommand(\"copy\");\n document.body.removeChild(ta);\n done?ok():fail();\n }catch(_){fail();}\n };\n if(navigator.clipboard&&typeof navigator.clipboard.writeText===\"function\"){\n navigator.clipboard.writeText(p).then(ok,fallback);\n }else fallback();\n}\n\n/* tiny zero-dependency markdown renderer (headings, lists, code, quote, inline) */\nfunction esc(s){return s.replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\");}\nfunction inlineMd(s){\n s=esc(s);\n s=s.replace(/`([^`]+)`/g,'<code>$1</code>');\n s=s.replace(/\\*\\*([^*]+)\\*\\*/g,'<strong>$1</strong>');\n s=s.replace(/(^|[^*])\\*([^*]+)\\*/g,'$1<em>$2</em>');\n s=s.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g,function(_,t,u){return /^(?:https?:|[/#.])/i.test(u)?'<a href=\"'+u+'\">'+t+'</a>':t;});\n return s;\n}\nfunction mdToHtml(md){\n const lines=(md||\"\").split(\"\\n\");\n let out=[], i=0, inCode=false, code=[], list=false;\n const closeList=()=>{if(list){out.push(\"</ul>\");list=false;}};\n while(i<lines.length){\n let ln=lines[i];\n if(/^```/.test(ln)){\n if(!inCode){inCode=true;code=[];}\n else{inCode=false;out.push(\"<pre><code>\"+esc(code.join(\"\\n\"))+\"</code></pre>\");}\n i++;continue;\n }\n if(inCode){code.push(ln);i++;continue;}\n let m;\n if((m=ln.match(/^(#{1,3})\\s+(.*)$/))){closeList();out.push(`<h${m[1].length}>`+inlineMd(m[2])+`</h${m[1].length}>`);}\n else if(/^\\s*[-*]\\s+/.test(ln)){if(!list){out.push(\"<ul>\");list=true;}out.push(\"<li>\"+inlineMd(ln.replace(/^\\s*[-*]\\s+/,\"\"))+\"</li>\");}\n else if(/^\\s*>\\s?/.test(ln)){closeList();out.push(\"<blockquote>\"+inlineMd(ln.replace(/^\\s*>\\s?/,\"\"))+\"</blockquote>\");}\n else if(ln.trim()===\"\"){closeList();}\n else{closeList();out.push(\"<p>\"+inlineMd(ln)+\"</p>\");}\n i++;\n }\n closeList();\n if(inCode)out.push(\"<pre><code>\"+esc(code.join(\"\\n\"))+\"</code></pre>\");\n return out.join(\"\\n\");\n}\n\nfunction markerBar(markers){\n const total=Object.values(markers||{}).reduce((a,b)=>a+b,0);\n const bar=el('div','bar'); if(!total) return bar;\n TIER_ORDER.forEach(t=>{const v=markers[t]||0;if(!v)return;\n const s=el('span');s.style.width=(v/total*100)+'%';s.style.background=TIER_COLOR[t];s.title=t+\": \"+v;bar.appendChild(s);});\n return bar;\n}\nfunction markersFromMarks(m){const o={};for(const k in (m||{})){const t=MARK2TIER[k];if(t)o[t]=(o[t]||0)+m[k];}return o;}\nfunction totalMarks(m){return Object.values(m||{}).reduce((a,b)=>a+b,0);}\n\nfunction go(node){stack.push(node);render();}\nfunction reset(s){spine=s;stack=[];render();}\nfunction crumbTo(i){stack=stack.slice(0,i);render();}\nfunction setSpine(s){document.querySelectorAll('nav.spines button').forEach(b=>b.classList.toggle('active',b.dataset.spine===s));}\n\nfunction render(){\n setSpine(spine);\n const view=document.getElementById('view');view.innerHTML='';\n renderCrumbs();\n if(document.getElementById('q').value.trim()){renderSearch(view);return;}\n if(stack.length===0){ ({rules:overviewRules,tasks:overviewTasks,lifecycle:renderLifecycle})[spine](view); return; }\n stack[stack.length-1].render(view);\n}\nfunction renderCrumbs(){\n const c=document.getElementById('crumbs');c.innerHTML='';\n const root=el('a','',spine[0].toUpperCase()+spine.slice(1));root.onclick=()=>{stack=[];render();};c.appendChild(root);\n stack.forEach((n,i)=>{c.appendChild(el('span','sep','\u203A'));\n if(i<stack.length-1){const a=el('a','',n.label);a.onclick=()=>crumbTo(i+1);c.appendChild(a);}\n else c.appendChild(el('span','here',n.label));});\n}\nfunction intro(t){return el('div','lead',t);}\nfunction ghdr(t){return el('h3','grouphdr',t);}\nfunction pathBar(p){const box=el('div','srcbox');box.appendChild(el('div','path',p));\n const b=el('button','act ghost','Copy path');b.onclick=e=>{e.stopPropagation();copyPath(p);};\n const btns=el('div','btns');btns.appendChild(b);box.appendChild(btns);return box;}\n\nfunction overviewRules(view){\n document.getElementById('foot').textContent=\n `${DATA.groupings.length} rule groupings \u00B7 ${DATA.groupings.reduce((a,g)=>a+g.doc_count,0)} docs \u00B7 derived from content/`;\n view.appendChild(intro(\"Major rule groupings \u2014 lazy-loaded bodies of guidance the agent pulls in only when the work needs it. Click to drill in.\"));\n const grid=el('div','grid');\n DATA.groupings.forEach(g=>{\n const card=el('div','card');\n card.appendChild(el('div','name',g.name));\n card.appendChild(el('div','desc',g.purpose||''));\n const meta=el('div','meta');meta.appendChild(el('span','',g.doc_count+' docs'));meta.appendChild(markerBar(g.markers));card.appendChild(meta);\n card.onclick=()=>go({label:g.name,render:v=>groupView(v,g)});\n grid.appendChild(card);\n });\n view.appendChild(grid);\n}\nfunction groupView(view,g){\n view.appendChild(intro(g.purpose||''));\n const files=g.items.filter(i=>i.kind==='file'), dirs=g.items.filter(i=>i.kind==='dir');\n if(files.length){view.appendChild(ghdr(\"Documents\"));view.appendChild(rowsOf(files));}\n if(dirs.length){view.appendChild(ghdr(\"Topics\"));\n const grid=el('div','grid');\n dirs.forEach(d=>{const card=el('div','card');card.appendChild(el('div','name',d.title||d.name));\n if(d.summary)card.appendChild(el('div','desc',d.summary));\n const meta=el('div','meta');meta.appendChild(el('span','',d.count+' files'));card.appendChild(meta);\n card.onclick=()=>go({label:d.name,render:v=>dirView(v,d)});grid.appendChild(card);});\n view.appendChild(grid);\n }\n}\nfunction dirView(view,d){\n view.appendChild(intro(d.summary||''));\n if(d.readme){view.appendChild(ghdr(\"Overview\"));view.appendChild(rowsOf([d.readme]));}\n view.appendChild(ghdr(\"Files\"));view.appendChild(rowsOf(d.files));\n}\nfunction rowsOf(files){\n const box=el('div','rows');\n files.forEach(f=>{\n const row=el('div','row');\n const nm=el('div','rname');nm.appendChild(document.createTextNode(f.title||f.name));\n if(f.summary)nm.appendChild(el('span','sub',f.summary));\n row.appendChild(nm);\n const rt=el('div','rt');const mk=markersFromMarks(f.markers);\n TIER_ORDER.forEach(t=>{if(mk[t]){const b=el('span','tier '+tierClass(t),mk[t]+\" \"+t.replace(\" NOT\",\"\u0338\"));b.title=mk[t]+\" \"+t;rt.appendChild(b);}});\n row.appendChild(rt);row.appendChild(el('span','chev','\u203A'));\n row.onclick=()=>go({label:f.name,render:v=>docDetail(v,f)});\n box.appendChild(row);\n });\n return box;\n}\nfunction docDetail(view,f){\n const d=el('div','detail');\n d.appendChild(el('div','kind',(f.generated?'generated view \u00B7 ':'')+(f.lines?f.lines+' lines':'document')));\n d.appendChild(el('h2',null,f.title||f.name));\n if(f.summary)d.appendChild(el('div','summary',f.summary));\n const mk=markersFromMarks(f.markers);\n if(totalMarks(f.markers)){const p=el('div');p.style.margin='0 0 14px';\n TIER_ORDER.forEach(t=>{if(mk[t]){const b=el('span','tier '+tierClass(t));b.textContent=mk[t]+\" \"+t;b.style.marginRight='6px';p.appendChild(b);}});\n d.appendChild(p);}\n if(f.sections&&f.sections.length){d.appendChild(el('div','muted','Sections'));\n const sl=el('div','sectionlist');f.sections.forEach(s=>sl.appendChild(el('div','s',s)));d.appendChild(sl);}\n if(f.generated)d.appendChild(Object.assign(el('div','muted'),{textContent:'\u26A0 Auto-generated from a pack \u2014 edit the source pack, not this file.',style:'margin-top:12px'}));\n\n const box=el('div','srcbox');\n box.appendChild(el('div','path',f.path));\n const btns=el('div','btns');\n const srcPane=el('div','src');srcPane.style.display='none';\n let shown=false;\n const view_=el('button','act','View source');\n view_.onclick=e=>{e.stopPropagation();shown=!shown;\n if(shown&&!srcPane.dataset.rendered){srcPane.innerHTML=mdToHtml(f.body||'(source not captured)');\n if(f.truncated)srcPane.appendChild(Object.assign(el('div','trunc'),{textContent:'\u2026 truncated (large file); open the file directly for the rest.'}));\n srcPane.dataset.rendered='1';}\n srcPane.style.display=shown?'block':'none';view_.textContent=shown?'Hide source':'View source';};\n const copy=el('button','act ghost','Copy path');copy.onclick=e=>{e.stopPropagation();copyPath(f.path);};\n btns.appendChild(view_);btns.appendChild(copy);box.appendChild(btns);box.appendChild(srcPane);\n d.appendChild(box);\n view.appendChild(d);\n}\n\nfunction overviewTasks(view){\n document.getElementById('foot').textContent=\n `${DATA.tasks.length} task namespaces \u00B7 ${DATA.tasks.reduce((a,t)=>a+t.task_count,0)} tasks \u00B7 run: task <namespace>:<name>`;\n view.appendChild(intro(\"The executable layer. Rules are enforced by Taskfile targets \u2014 deterministic gates the agent and CI run. Each card is a namespace; drill in for its commands.\"));\n const grid=el('div','grid');\n DATA.tasks.forEach(t=>{const card=el('div','card');\n const nm=el('div','name');nm.appendChild(document.createTextNode(t.namespace));\n if(t.unlisted)nm.appendChild(Object.assign(el('span','ic'),{textContent:'unlisted'}));\n card.appendChild(nm);card.appendChild(el('div','desc',t.purpose||''));\n const meta=el('div','meta');meta.appendChild(el('span','',t.task_count+' tasks'));card.appendChild(meta);\n card.onclick=()=>go({label:t.namespace,render:v=>taskDetail(v,t)});grid.appendChild(card);});\n view.appendChild(grid);\n}\nfunction taskDetail(view,t){\n view.appendChild(intro(t.purpose||''));\n view.appendChild(ghdr(\"Commands\"));\n const box=el('div','rows');\n t.tasks.forEach(tk=>{const row=el('div','row');row.style.cursor='default';\n const nm=el('div','rname');nm.appendChild(Object.assign(el('code'),{textContent:'task '+t.namespace+':'+tk.name}));\n if(tk.desc)nm.appendChild(el('span','sub',tk.desc));row.appendChild(nm);box.appendChild(row);});\n view.appendChild(box);\n view.appendChild(pathBar(t.path));\n}\nfunction renderLifecycle(view){\n const L=DATA.lifecycle;\n document.getElementById('foot').textContent=\"Lifecycle & gates \u00B7 source: \"+L.source;\n view.appendChild(intro(L.summary));\n view.appendChild(ghdr(\"Rule strength \u2014 prefer enforceable over remembered\"));\n const lad=el('div','ladder');\n L.rule_strength.forEach((r,i)=>{const lv=el('div','lv');lv.appendChild(Object.assign(el('b'),{textContent:(i+1)+\". \"+r.level}));lv.appendChild(el('span','d',r.detail));lad.appendChild(lv);});\n view.appendChild(lad);\n view.appendChild(ghdr(\"Scope lifecycle\"));\n const flow=el('div','flow');\n L.scope_states.forEach((s,i)=>{if(i)flow.appendChild(el('div','arw','\u2192'));\n const n=el('div','node');n.appendChild(el('div','st',s.state));n.appendChild(el('div','via',s.via));flow.appendChild(n);});\n view.appendChild(flow);\n view.appendChild(ghdr(\"vBRIEF \u2014 the durable state\"));\n const box=el('div','rows');\n L.vbrief_files.forEach(v=>{const row=el('div','row');row.style.cursor='default';\n const nm=el('div','rname');nm.appendChild(Object.assign(el('code'),{textContent:v.file}));nm.appendChild(el('span','sub',v.role));row.appendChild(nm);box.appendChild(row);});\n view.appendChild(box);\n view.appendChild(ghdr(\"Quality gates\"));\n const g=el('div');g.style.marginBottom='14px';\n L.gates.forEach(x=>{const p=el('span','pill');p.appendChild(Object.assign(el('code'),{textContent:x}));g.appendChild(p);});\n view.appendChild(g);\n view.appendChild(ghdr(\"Key docs\"));\n const kd=el('div','rows');\n L.key_docs.forEach(doc=>{const row=el('div','row');row.style.cursor='default';\n const nm=el('div','rname');nm.appendChild(document.createTextNode(doc.title));nm.appendChild(el('span','sub',doc.path));row.appendChild(nm);\n const b=el('button','act ghost','Copy path');b.onclick=e=>{e.stopPropagation();copyPath(doc.path);};\n const rt=el('div','rt');rt.appendChild(b);row.appendChild(rt);kd.appendChild(row);});\n view.appendChild(kd);\n}\nfunction renderSearch(view){\n const q=document.getElementById('q').value.trim().toLowerCase();\n const hits=[];\n const push=(label,sub,onclick,tag)=>hits.push({label,sub,onclick,tag});\n DATA.groupings.forEach(g=>{\n if((g.name+\" \"+g.purpose).toLowerCase().includes(q))\n push(g.name,g.purpose,()=>{spine='rules';stack=[{label:g.name,render:v=>groupView(v,g)}];document.getElementById('q').value='';render();},\"grouping\");\n g.items.forEach(it=>{\n const scan=f=>{if((f.title+\" \"+f.name+\" \"+(f.summary||\"\")).toLowerCase().includes(q))\n push(f.title||f.name,f.summary||g.name,()=>{spine='rules';stack=[{label:g.name,render:v=>groupView(v,g)},{label:f.name,render:v=>docDetail(v,f)}];document.getElementById('q').value='';render();},g.name);};\n if(it.kind==='file')scan(it); else{if(it.readme)scan(it.readme);it.files.forEach(scan);}\n });\n });\n DATA.tasks.forEach(t=>{\n if((t.namespace+\" \"+t.purpose).toLowerCase().includes(q))\n push(\"task \"+t.namespace,t.purpose,()=>{spine='tasks';stack=[{label:t.namespace,render:v=>taskDetail(v,t)}];document.getElementById('q').value='';render();},\"tasks\");\n t.tasks.forEach(tk=>{if((tk.name+\" \"+tk.desc).toLowerCase().includes(q))\n push(\"task \"+t.namespace+\":\"+tk.name,tk.desc,()=>{spine='tasks';stack=[{label:t.namespace,render:v=>taskDetail(v,t)}];document.getElementById('q').value='';render();},\"task\");});\n });\n document.getElementById('foot').textContent=hits.length+\" matches\";\n if(!hits.length){view.appendChild(el('div','empty','No matches for \"'+q+'\"'));return;}\n const box=el('div','rows');\n hits.slice(0,120).forEach(h=>{const row=el('div','row');\n const nm=el('div','rname');nm.appendChild(document.createTextNode(h.label));if(h.sub)nm.appendChild(el('span','sub',h.sub));row.appendChild(nm);\n if(h.tag){const rt=el('div','rt');rt.appendChild(el('span','pill',h.tag));row.appendChild(rt);}\n row.appendChild(el('span','chev','\u203A'));row.onclick=h.onclick;box.appendChild(row);});\n view.appendChild(box);\n}\n\ndocument.querySelectorAll('nav.spines button').forEach(b=>b.onclick=()=>reset(b.dataset.spine));\nconst qEl=document.getElementById('q');\nqEl.addEventListener('input',render);\nqEl.addEventListener('keydown',e=>{if(e.key==='Escape'){qEl.value='';render();}});\n\nif(!DATA){document.getElementById('view').innerHTML='<div class=\"empty\">No data embedded. Rebuild with <code>task docs:rule-map</code>.</div>';}\nelse{document.getElementById('sub').textContent=\"Deft Directive \u00B7 \"+DATA.groupings.length+\" rule groups \u00B7 \"+DATA.tasks.length+\" task namespaces \u00B7 maintainer map\";render();}\n</script>\n</body>\n</html>\n";
|
|
2
|
+
//# sourceMappingURL=rule-map-template.d.ts.map
|