@deftai/directive-core 0.92.0 → 0.94.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 +29 -0
- package/dist/cache/fetch.js +131 -4
- package/dist/cache/operations.d.ts +8 -0
- package/dist/cache/operations.js +67 -4
- package/dist/check/cached-orchestrator.js +11 -0
- package/dist/check/consumer-gate-integrity.d.ts +68 -0
- package/dist/check/consumer-gate-integrity.js +265 -0
- package/dist/check/index.d.ts +1 -0
- package/dist/check/index.js +1 -0
- package/dist/check/orchestrator.js +10 -0
- package/dist/content-contracts/skills/greptile-detector.d.ts +9 -0
- package/dist/content-contracts/skills/greptile-detector.js +9 -2
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +69 -0
- package/dist/doctor/openclaw-l2-adapter.d.ts +26 -0
- package/dist/doctor/openclaw-l2-adapter.js +199 -0
- package/dist/finish-loop/pr-finish-loop.js +1 -0
- package/dist/hooks/dispatcher.d.ts +24 -2
- package/dist/hooks/dispatcher.js +122 -11
- package/dist/hooks/readonly.d.ts +14 -0
- package/dist/hooks/readonly.js +126 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/hygiene.js +22 -0
- package/dist/init-deposit/index.d.ts +3 -0
- package/dist/init-deposit/index.js +3 -0
- package/dist/init-deposit/init-deposit.js +10 -0
- package/dist/init-deposit/refresh.js +11 -0
- package/dist/init-deposit/scaffold.js +3 -133
- package/dist/init-deposit/skill-discovery-deposit.d.ts +65 -0
- package/dist/init-deposit/skill-discovery-deposit.js +287 -0
- package/dist/init-deposit/skill-discovery-hosts.d.ts +94 -0
- package/dist/init-deposit/skill-discovery-hosts.js +217 -0
- package/dist/init-deposit/slash-deposit.d.ts +46 -0
- package/dist/init-deposit/slash-deposit.js +174 -0
- package/dist/intake/issue-ingest.d.ts +20 -0
- package/dist/intake/issue-ingest.js +58 -0
- package/dist/policy/host-slash-commands.d.ts +28 -0
- package/dist/policy/host-slash-commands.js +103 -0
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +61 -7
- package/dist/policy/min-greptile-confidence.d.ts +57 -0
- package/dist/policy/min-greptile-confidence.js +123 -0
- package/dist/pr-merge-readiness/compute.d.ts +11 -1
- package/dist/pr-merge-readiness/compute.js +17 -4
- package/dist/pr-merge-readiness/evaluate.d.ts +8 -1
- package/dist/pr-merge-readiness/evaluate.js +7 -4
- package/dist/pr-merge-readiness/mergeability.d.ts +7 -1
- package/dist/pr-merge-readiness/mergeability.js +14 -2
- package/dist/pr-monitor/main.d.ts +2 -0
- package/dist/pr-monitor/main.js +31 -1
- package/dist/pr-monitor/monitor.js +5 -1
- package/dist/pr-monitor/readiness.js +3 -1
- package/dist/pr-monitor/types.d.ts +8 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +8 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -1
- package/dist/pr-wait-mergeable/main.d.ts +2 -0
- package/dist/pr-wait-mergeable/main.js +16 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +3 -0
- package/dist/pr-watch/constants.d.ts +4 -1
- package/dist/pr-watch/constants.js +5 -2
- package/dist/pr-watch/main.js +1 -0
- package/dist/pr-watch/probe.d.ts +1 -1
- package/dist/pr-watch/probe.js +4 -1
- package/dist/pr-watch/types.d.ts +7 -1
- package/dist/pr-watch/watch.js +3 -2
- package/dist/review-monitor/index.d.ts +1 -0
- package/dist/review-monitor/index.js +1 -0
- package/dist/review-monitor/l4-owner.d.ts +46 -0
- package/dist/review-monitor/l4-owner.js +262 -0
- package/dist/slash/emitters.d.ts +102 -0
- package/dist/slash/emitters.js +148 -0
- package/dist/slash/generator.d.ts +98 -0
- package/dist/slash/generator.js +145 -0
- package/dist/slash/index.d.ts +16 -0
- package/dist/slash/index.js +16 -0
- package/dist/slash/openclaw-adapter.d.ts +64 -0
- package/dist/slash/openclaw-adapter.js +198 -0
- package/dist/slash/openclaw-deposit.d.ts +73 -0
- package/dist/slash/openclaw-deposit.js +279 -0
- package/dist/slash/openclaw-slugs.d.ts +52 -0
- package/dist/slash/openclaw-slugs.js +126 -0
- package/dist/slash/product-set.d.ts +50 -0
- package/dist/slash/product-set.js +142 -0
- package/dist/tool-events/classify.js +1 -0
- package/dist/triage/welcome/writers.js +16 -7
- package/dist/umbrella-current-shape/index.d.ts +45 -0
- package/dist/umbrella-current-shape/index.js +162 -8
- package/dist/vbrief-reconcile/index.d.ts +2 -2
- package/dist/vbrief-reconcile/index.js +1 -1
- package/dist/vbrief-reconcile/types.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +31 -2
- package/dist/vbrief-reconcile/umbrellas.js +231 -19
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +50 -0
- package/dist/xbrief/create.d.ts +36 -0
- package/dist/xbrief/create.js +285 -0
- package/dist/xbrief/index.d.ts +14 -0
- package/dist/xbrief/index.js +42 -0
- package/dist/xbrief/paths.d.ts +37 -0
- package/dist/xbrief/paths.js +123 -0
- package/dist/xbrief/styles.d.ts +36 -0
- package/dist/xbrief/styles.js +235 -0
- package/dist/xbrief/types.d.ts +50 -0
- package/dist/xbrief/types.js +17 -0
- package/dist/xbrief/verify.d.ts +30 -0
- package/dist/xbrief/verify.js +251 -0
- package/package.json +11 -3
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-host skill discovery layouts + per-host opt-out (#75 residual).
|
|
3
|
+
*
|
|
4
|
+
* Deposits **skill** discovery dirs (thin SKILL.md pointers). Distinct from
|
|
5
|
+
* epic #55 slash/command paths (`.claude/commands/`, `.codex/prompts/`, …).
|
|
6
|
+
*
|
|
7
|
+
* | Host id | Relative skills directory |
|
|
8
|
+
* |---------|---------------------------|
|
|
9
|
+
* | claude | `.claude/skills` |
|
|
10
|
+
* | cursor | `.cursor/skills` |
|
|
11
|
+
* | codex | `.codex/skills` |
|
|
12
|
+
* | github | `.github/skills` |
|
|
13
|
+
*
|
|
14
|
+
* Policy: `plan.policy.hostSkillDiscovery` — per-host boolean, default all true.
|
|
15
|
+
* Opt-out skips deposit for that host only.
|
|
16
|
+
*/
|
|
17
|
+
/** Hosts with a known residual skill-discovery path matrix (#75). */
|
|
18
|
+
export declare const SKILL_DISCOVERY_HOSTS: readonly ["claude", "cursor", "codex", "github"];
|
|
19
|
+
export type SkillDiscoveryHostId = (typeof SKILL_DISCOVERY_HOSTS)[number];
|
|
20
|
+
/** Repo-relative layout for one host’s skill discovery tree. */
|
|
21
|
+
export interface HostSkillDiscoveryLayout {
|
|
22
|
+
readonly hostId: SkillDiscoveryHostId;
|
|
23
|
+
/**
|
|
24
|
+
* Repo-relative directory (posix, no trailing slash).
|
|
25
|
+
* Example: `.claude/skills`
|
|
26
|
+
*/
|
|
27
|
+
readonly relativeDir: string;
|
|
28
|
+
/** Typical agent product that scans this path. */
|
|
29
|
+
readonly typicalHost: string;
|
|
30
|
+
/**
|
|
31
|
+
* Filename under `{relativeDir}/{skillDir}/` (always `SKILL.md` for v1 —
|
|
32
|
+
* same thin-pointer format as `.agents/skills/`).
|
|
33
|
+
*/
|
|
34
|
+
readonly skillFilename: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Documented host id → skills directory mapping (#75 residual matrix).
|
|
38
|
+
* Frozen; additive registration only.
|
|
39
|
+
*/
|
|
40
|
+
export declare const HOST_SKILL_DISCOVERY_LAYOUTS: Readonly<Record<SkillDiscoveryHostId, HostSkillDiscoveryLayout>>;
|
|
41
|
+
export declare const FIELD_HOST_SKILL_DISCOVERY = "plan.policy.hostSkillDiscovery";
|
|
42
|
+
export declare const FIELD_HOST_SKILL_DISCOVERY_CLI_ALIAS = "hostSkillDiscovery";
|
|
43
|
+
/** Per-host skill discovery deposit toggles (#75). Default: all enabled. */
|
|
44
|
+
export type HostSkillDiscoveryPolicy = Record<SkillDiscoveryHostId, boolean>;
|
|
45
|
+
export declare const DEFAULT_HOST_SKILL_DISCOVERY_POLICY: HostSkillDiscoveryPolicy;
|
|
46
|
+
export interface HostSkillDiscoveryPolicyField {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly current: HostSkillDiscoveryPolicy;
|
|
49
|
+
readonly default: HostSkillDiscoveryPolicy;
|
|
50
|
+
readonly source: string;
|
|
51
|
+
}
|
|
52
|
+
/** Type guard for {@link SkillDiscoveryHostId}. */
|
|
53
|
+
export declare function isSkillDiscoveryHostId(value: string): value is SkillDiscoveryHostId;
|
|
54
|
+
/** Stable list of residual skill-discovery hosts. */
|
|
55
|
+
export declare function listSkillDiscoveryHosts(): readonly SkillDiscoveryHostId[];
|
|
56
|
+
/** Look up the documented layout for a host, or throw. */
|
|
57
|
+
export declare function getHostSkillDiscoveryLayout(hostId: SkillDiscoveryHostId): HostSkillDiscoveryLayout;
|
|
58
|
+
export interface ResolvedHostSkillDiscoveryPolicy {
|
|
59
|
+
readonly policy: HostSkillDiscoveryPolicy;
|
|
60
|
+
/** Non-empty when raw policy was malformed; callers SHOULD surface these. */
|
|
61
|
+
readonly warnings: readonly string[];
|
|
62
|
+
/**
|
|
63
|
+
* True when the entire raw value is unusable (not an object). Deposit SHOULD
|
|
64
|
+
* skip all hosts rather than silently apply defaults after a bad opt-out.
|
|
65
|
+
*/
|
|
66
|
+
readonly refuseAll: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve typed host skill discovery policy from raw PROJECT-DEFINITION value.
|
|
70
|
+
* Fail-closed on malformed opt-outs (#75 Greptile P1): bad host values disable
|
|
71
|
+
* that host; wholly non-object values refuse all multi-host deposit.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveHostSkillDiscoveryPolicyDetailed(raw: unknown): ResolvedHostSkillDiscoveryPolicy;
|
|
74
|
+
/** Resolve typed host skill discovery policy from raw PROJECT-DEFINITION value. */
|
|
75
|
+
export declare function resolveHostSkillDiscoveryPolicy(raw: unknown): HostSkillDiscoveryPolicy;
|
|
76
|
+
export declare function validateHostSkillDiscovery(value: unknown): string[];
|
|
77
|
+
export declare function isHostSkillDiscoveryEnabled(host: SkillDiscoveryHostId, policy?: HostSkillDiscoveryPolicy): boolean;
|
|
78
|
+
/** Inspector row for `policy:show --field=hostSkillDiscovery`. */
|
|
79
|
+
export declare function inspectHostSkillDiscovery(data: Record<string, unknown> | null): HostSkillDiscoveryPolicyField;
|
|
80
|
+
/** Load raw hostSkillDiscovery value from PROJECT-DEFINITION, if present. */
|
|
81
|
+
export declare function loadHostSkillDiscoveryRawFromProject(projectRoot: string): unknown;
|
|
82
|
+
/** Resolve host skill discovery policy from PROJECT-DEFINITION on disk. */
|
|
83
|
+
export declare function loadHostSkillDiscoveryPolicyFromProject(projectRoot: string): HostSkillDiscoveryPolicy;
|
|
84
|
+
/**
|
|
85
|
+
* Resolve policy + validation warnings from PROJECT-DEFINITION for deposit.
|
|
86
|
+
* Surfaces malformed opt-outs so init/update can report them (#75 Greptile P1).
|
|
87
|
+
*/
|
|
88
|
+
export declare function loadHostSkillDiscoveryPolicyDetailedFromProject(projectRoot: string): ResolvedHostSkillDiscoveryPolicy;
|
|
89
|
+
/**
|
|
90
|
+
* Repo-relative posix path for one skill under a host layout.
|
|
91
|
+
* Example: `.claude/skills/deft-directive-build/SKILL.md`
|
|
92
|
+
*/
|
|
93
|
+
export declare function hostSkillRelativePath(hostId: SkillDiscoveryHostId, skillDir: string): string;
|
|
94
|
+
//# sourceMappingURL=skill-discovery-hosts.d.ts.map
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-host skill discovery layouts + per-host opt-out (#75 residual).
|
|
3
|
+
*
|
|
4
|
+
* Deposits **skill** discovery dirs (thin SKILL.md pointers). Distinct from
|
|
5
|
+
* epic #55 slash/command paths (`.claude/commands/`, `.codex/prompts/`, …).
|
|
6
|
+
*
|
|
7
|
+
* | Host id | Relative skills directory |
|
|
8
|
+
* |---------|---------------------------|
|
|
9
|
+
* | claude | `.claude/skills` |
|
|
10
|
+
* | cursor | `.cursor/skills` |
|
|
11
|
+
* | codex | `.codex/skills` |
|
|
12
|
+
* | github | `.github/skills` |
|
|
13
|
+
*
|
|
14
|
+
* Policy: `plan.policy.hostSkillDiscovery` — per-host boolean, default all true.
|
|
15
|
+
* Opt-out skips deposit for that host only.
|
|
16
|
+
*/
|
|
17
|
+
import { readPlanPolicy } from "../policy/plan-extensions.js";
|
|
18
|
+
import { loadProjectDefinition } from "../policy/resolve.js";
|
|
19
|
+
/** Hosts with a known residual skill-discovery path matrix (#75). */
|
|
20
|
+
export const SKILL_DISCOVERY_HOSTS = ["claude", "cursor", "codex", "github"];
|
|
21
|
+
/**
|
|
22
|
+
* Documented host id → skills directory mapping (#75 residual matrix).
|
|
23
|
+
* Frozen; additive registration only.
|
|
24
|
+
*/
|
|
25
|
+
export const HOST_SKILL_DISCOVERY_LAYOUTS = Object.freeze({
|
|
26
|
+
claude: Object.freeze({
|
|
27
|
+
hostId: "claude",
|
|
28
|
+
relativeDir: ".claude/skills",
|
|
29
|
+
typicalHost: "Claude Code",
|
|
30
|
+
skillFilename: "SKILL.md",
|
|
31
|
+
}),
|
|
32
|
+
cursor: Object.freeze({
|
|
33
|
+
hostId: "cursor",
|
|
34
|
+
relativeDir: ".cursor/skills",
|
|
35
|
+
typicalHost: "Cursor",
|
|
36
|
+
skillFilename: "SKILL.md",
|
|
37
|
+
}),
|
|
38
|
+
codex: Object.freeze({
|
|
39
|
+
hostId: "codex",
|
|
40
|
+
relativeDir: ".codex/skills",
|
|
41
|
+
typicalHost: "OpenAI Codex",
|
|
42
|
+
skillFilename: "SKILL.md",
|
|
43
|
+
}),
|
|
44
|
+
github: Object.freeze({
|
|
45
|
+
hostId: "github",
|
|
46
|
+
relativeDir: ".github/skills",
|
|
47
|
+
typicalHost: "GitHub Copilot",
|
|
48
|
+
skillFilename: "SKILL.md",
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
export const FIELD_HOST_SKILL_DISCOVERY = "plan.policy.hostSkillDiscovery";
|
|
52
|
+
export const FIELD_HOST_SKILL_DISCOVERY_CLI_ALIAS = "hostSkillDiscovery";
|
|
53
|
+
export const DEFAULT_HOST_SKILL_DISCOVERY_POLICY = {
|
|
54
|
+
claude: true,
|
|
55
|
+
cursor: true,
|
|
56
|
+
codex: true,
|
|
57
|
+
github: true,
|
|
58
|
+
};
|
|
59
|
+
/** Type guard for {@link SkillDiscoveryHostId}. */
|
|
60
|
+
export function isSkillDiscoveryHostId(value) {
|
|
61
|
+
return SKILL_DISCOVERY_HOSTS.includes(value);
|
|
62
|
+
}
|
|
63
|
+
/** Stable list of residual skill-discovery hosts. */
|
|
64
|
+
export function listSkillDiscoveryHosts() {
|
|
65
|
+
return SKILL_DISCOVERY_HOSTS;
|
|
66
|
+
}
|
|
67
|
+
/** Look up the documented layout for a host, or throw. */
|
|
68
|
+
export function getHostSkillDiscoveryLayout(hostId) {
|
|
69
|
+
const layout = HOST_SKILL_DISCOVERY_LAYOUTS[hostId];
|
|
70
|
+
if (layout === undefined) {
|
|
71
|
+
throw new Error(`No skill discovery layout for host: ${hostId}`);
|
|
72
|
+
}
|
|
73
|
+
return layout;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve one host toggle. Malformed present values fail closed to **disabled**
|
|
77
|
+
* (do not silently enable deposit when the operator attempted an opt-out shape).
|
|
78
|
+
*/
|
|
79
|
+
function readHostBoolean(rec, host, fallback, warnings) {
|
|
80
|
+
if (!(host in rec)) {
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
const value = rec[host];
|
|
84
|
+
if (typeof value === "boolean") {
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
warnings.push(`${FIELD_HOST_SKILL_DISCOVERY}.${host} must be a boolean (got ${typeof value}); treating as disabled`);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolve typed host skill discovery policy from raw PROJECT-DEFINITION value.
|
|
92
|
+
* Fail-closed on malformed opt-outs (#75 Greptile P1): bad host values disable
|
|
93
|
+
* that host; wholly non-object values refuse all multi-host deposit.
|
|
94
|
+
*/
|
|
95
|
+
export function resolveHostSkillDiscoveryPolicyDetailed(raw) {
|
|
96
|
+
if (raw === null || raw === undefined) {
|
|
97
|
+
return {
|
|
98
|
+
policy: { ...DEFAULT_HOST_SKILL_DISCOVERY_POLICY },
|
|
99
|
+
warnings: [],
|
|
100
|
+
refuseAll: false,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
104
|
+
return {
|
|
105
|
+
policy: {
|
|
106
|
+
claude: false,
|
|
107
|
+
cursor: false,
|
|
108
|
+
codex: false,
|
|
109
|
+
github: false,
|
|
110
|
+
},
|
|
111
|
+
warnings: [
|
|
112
|
+
`${FIELD_HOST_SKILL_DISCOVERY} must be an object; got ${typeof raw} — multi-host skill discovery deposit skipped`,
|
|
113
|
+
],
|
|
114
|
+
refuseAll: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const rec = raw;
|
|
118
|
+
const warnings = [];
|
|
119
|
+
for (const key of Object.keys(rec)) {
|
|
120
|
+
if (!isSkillDiscoveryHostId(key)) {
|
|
121
|
+
warnings.push(`${FIELD_HOST_SKILL_DISCOVERY}.${key} is not a skill-discovery host (${SKILL_DISCOVERY_HOSTS.join(", ")})`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const policy = {
|
|
125
|
+
claude: readHostBoolean(rec, "claude", DEFAULT_HOST_SKILL_DISCOVERY_POLICY.claude, warnings),
|
|
126
|
+
cursor: readHostBoolean(rec, "cursor", DEFAULT_HOST_SKILL_DISCOVERY_POLICY.cursor, warnings),
|
|
127
|
+
codex: readHostBoolean(rec, "codex", DEFAULT_HOST_SKILL_DISCOVERY_POLICY.codex, warnings),
|
|
128
|
+
github: readHostBoolean(rec, "github", DEFAULT_HOST_SKILL_DISCOVERY_POLICY.github, warnings),
|
|
129
|
+
};
|
|
130
|
+
return { policy, warnings, refuseAll: false };
|
|
131
|
+
}
|
|
132
|
+
/** Resolve typed host skill discovery policy from raw PROJECT-DEFINITION value. */
|
|
133
|
+
export function resolveHostSkillDiscoveryPolicy(raw) {
|
|
134
|
+
return resolveHostSkillDiscoveryPolicyDetailed(raw).policy;
|
|
135
|
+
}
|
|
136
|
+
export function validateHostSkillDiscovery(value) {
|
|
137
|
+
if (value === null || value === undefined) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
141
|
+
return [`${FIELD_HOST_SKILL_DISCOVERY} must be an object; got ${typeof value}`];
|
|
142
|
+
}
|
|
143
|
+
const rec = value;
|
|
144
|
+
const errors = [];
|
|
145
|
+
for (const host of SKILL_DISCOVERY_HOSTS) {
|
|
146
|
+
if (host in rec && typeof rec[host] !== "boolean") {
|
|
147
|
+
errors.push(`${FIELD_HOST_SKILL_DISCOVERY}.${host} must be a boolean`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const key of Object.keys(rec)) {
|
|
151
|
+
if (!isSkillDiscoveryHostId(key)) {
|
|
152
|
+
errors.push(`${FIELD_HOST_SKILL_DISCOVERY}.${key} is not a skill-discovery host (${SKILL_DISCOVERY_HOSTS.join(", ")})`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return errors;
|
|
156
|
+
}
|
|
157
|
+
export function isHostSkillDiscoveryEnabled(host, policy = DEFAULT_HOST_SKILL_DISCOVERY_POLICY) {
|
|
158
|
+
return policy[host];
|
|
159
|
+
}
|
|
160
|
+
function fieldFromResolved(resolved, source) {
|
|
161
|
+
return {
|
|
162
|
+
name: FIELD_HOST_SKILL_DISCOVERY,
|
|
163
|
+
current: resolved,
|
|
164
|
+
default: DEFAULT_HOST_SKILL_DISCOVERY_POLICY,
|
|
165
|
+
source,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/** Inspector row for `policy:show --field=hostSkillDiscovery`. */
|
|
169
|
+
export function inspectHostSkillDiscovery(data) {
|
|
170
|
+
if (data === null) {
|
|
171
|
+
return fieldFromResolved(DEFAULT_HOST_SKILL_DISCOVERY_POLICY, "default");
|
|
172
|
+
}
|
|
173
|
+
const policyBlock = readPlanPolicy(data.plan);
|
|
174
|
+
if (typeof policyBlock !== "object" ||
|
|
175
|
+
policyBlock === null ||
|
|
176
|
+
Array.isArray(policyBlock) ||
|
|
177
|
+
!("hostSkillDiscovery" in policyBlock)) {
|
|
178
|
+
return fieldFromResolved(DEFAULT_HOST_SKILL_DISCOVERY_POLICY, "default");
|
|
179
|
+
}
|
|
180
|
+
const resolved = resolveHostSkillDiscoveryPolicy(policyBlock.hostSkillDiscovery);
|
|
181
|
+
return fieldFromResolved(resolved, "typed");
|
|
182
|
+
}
|
|
183
|
+
/** Load raw hostSkillDiscovery value from PROJECT-DEFINITION, if present. */
|
|
184
|
+
export function loadHostSkillDiscoveryRawFromProject(projectRoot) {
|
|
185
|
+
const [data] = loadProjectDefinition(projectRoot);
|
|
186
|
+
if (data === null) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const policyBlock = readPlanPolicy(data.plan);
|
|
190
|
+
if (typeof policyBlock !== "object" ||
|
|
191
|
+
policyBlock === null ||
|
|
192
|
+
Array.isArray(policyBlock) ||
|
|
193
|
+
!("hostSkillDiscovery" in policyBlock)) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
return policyBlock.hostSkillDiscovery;
|
|
197
|
+
}
|
|
198
|
+
/** Resolve host skill discovery policy from PROJECT-DEFINITION on disk. */
|
|
199
|
+
export function loadHostSkillDiscoveryPolicyFromProject(projectRoot) {
|
|
200
|
+
return resolveHostSkillDiscoveryPolicy(loadHostSkillDiscoveryRawFromProject(projectRoot));
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Resolve policy + validation warnings from PROJECT-DEFINITION for deposit.
|
|
204
|
+
* Surfaces malformed opt-outs so init/update can report them (#75 Greptile P1).
|
|
205
|
+
*/
|
|
206
|
+
export function loadHostSkillDiscoveryPolicyDetailedFromProject(projectRoot) {
|
|
207
|
+
return resolveHostSkillDiscoveryPolicyDetailed(loadHostSkillDiscoveryRawFromProject(projectRoot));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Repo-relative posix path for one skill under a host layout.
|
|
211
|
+
* Example: `.claude/skills/deft-directive-build/SKILL.md`
|
|
212
|
+
*/
|
|
213
|
+
export function hostSkillRelativePath(hostId, skillDir) {
|
|
214
|
+
const layout = getHostSkillDiscoveryLayout(hostId);
|
|
215
|
+
return `${layout.relativeDir}/${skillDir}/${layout.skillFilename}`;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=skill-discovery-hosts.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-host init/update deposit for native slash command files (#3054 / epic #55).
|
|
3
|
+
*
|
|
4
|
+
* Wires #3053 emitters into the init/update deposit path:
|
|
5
|
+
* - Default enabled set = hosts with real emitters (L6)
|
|
6
|
+
* - Per-host opt-out via `plan.policy.hostSlashCommands`
|
|
7
|
+
* - Idempotent managed rewrite (no duplicate pile-up)
|
|
8
|
+
* - Prefer commit of **managed product paths only** (L8) — exact allowlist, not whole dirs
|
|
9
|
+
*
|
|
10
|
+
* Parallel to agent-hooks deposit; does not touch hook JSON configs.
|
|
11
|
+
*
|
|
12
|
+
* Ownership: only create/update/remove files that are missing or still look like
|
|
13
|
+
* Directive thin wrappers (`isThinWrapperMarkdown`). Consumer-customized content
|
|
14
|
+
* at a product filename is left untouched.
|
|
15
|
+
*/
|
|
16
|
+
import { type HostSlashCommandsPolicy } from "../policy/host-slash-commands.js";
|
|
17
|
+
import { type SlashEmitterHostId } from "../slash/index.js";
|
|
18
|
+
import type { InitDepositIo } from "./constants.js";
|
|
19
|
+
export interface SlashCommandDepositResult {
|
|
20
|
+
readonly changed: boolean;
|
|
21
|
+
/** Repo-relative paths written or updated. */
|
|
22
|
+
readonly writtenPaths: string[];
|
|
23
|
+
/** Repo-relative paths removed on opt-out (managed product files only). */
|
|
24
|
+
readonly removedPaths: string[];
|
|
25
|
+
/** Repo-relative paths skipped because consumer content is not managed. */
|
|
26
|
+
readonly preservedCustomPaths: string[];
|
|
27
|
+
/** Hosts that received a deposit pass (policy enabled). */
|
|
28
|
+
readonly depositedHosts: SlashEmitterHostId[];
|
|
29
|
+
/** Hosts skipped by policy opt-out. */
|
|
30
|
+
readonly skippedHosts: SlashEmitterHostId[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Write thin-wrapper command/prompt files for every policy-enabled host.
|
|
34
|
+
*
|
|
35
|
+
* Idempotent: skips files whose on-disk bytes already match emission.
|
|
36
|
+
* Ownership-safe: never overwrites non-thin consumer customizations at product paths.
|
|
37
|
+
* Opt-out: removes managed thin wrappers only (leaves user customizations alone).
|
|
38
|
+
*/
|
|
39
|
+
export declare function writeSlashCommandDeposit(projectRoot: string, io?: InitDepositIo, policy?: HostSlashCommandsPolicy): SlashCommandDepositResult;
|
|
40
|
+
/**
|
|
41
|
+
* Exact repo-relative product command paths (all emitter hosts).
|
|
42
|
+
* Used by installerManagedMatchers for L8 prefer-commit staging without
|
|
43
|
+
* claiming whole host command directories (consumer custom files stay app-owned).
|
|
44
|
+
*/
|
|
45
|
+
export declare function slashCommandManagedExactPaths(): readonly string[];
|
|
46
|
+
//# sourceMappingURL=slash-deposit.d.ts.map
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-host init/update deposit for native slash command files (#3054 / epic #55).
|
|
3
|
+
*
|
|
4
|
+
* Wires #3053 emitters into the init/update deposit path:
|
|
5
|
+
* - Default enabled set = hosts with real emitters (L6)
|
|
6
|
+
* - Per-host opt-out via `plan.policy.hostSlashCommands`
|
|
7
|
+
* - Idempotent managed rewrite (no duplicate pile-up)
|
|
8
|
+
* - Prefer commit of **managed product paths only** (L8) — exact allowlist, not whole dirs
|
|
9
|
+
*
|
|
10
|
+
* Parallel to agent-hooks deposit; does not touch hook JSON configs.
|
|
11
|
+
*
|
|
12
|
+
* Ownership: only create/update/remove files that are missing or still look like
|
|
13
|
+
* Directive thin wrappers (`isThinWrapperMarkdown`). Consumer-customized content
|
|
14
|
+
* at a product filename is left untouched.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from "node:fs";
|
|
17
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
18
|
+
import { assertDepositContained } from "../deposit/contain.js";
|
|
19
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
20
|
+
import { isHostSlashCommandDepositEnabled, loadHostSlashCommandsPolicyFromProject, } from "../policy/host-slash-commands.js";
|
|
21
|
+
import { emitHostCommandFiles, getHostCommandLayout, isThinWrapperMarkdown, listSlashEmitterHosts, } from "../slash/index.js";
|
|
22
|
+
import { listProductCommands, logicalIdToFilename } from "../slash/product-set.js";
|
|
23
|
+
/**
|
|
24
|
+
* Write thin-wrapper command/prompt files for every policy-enabled host.
|
|
25
|
+
*
|
|
26
|
+
* Idempotent: skips files whose on-disk bytes already match emission.
|
|
27
|
+
* Ownership-safe: never overwrites non-thin consumer customizations at product paths.
|
|
28
|
+
* Opt-out: removes managed thin wrappers only (leaves user customizations alone).
|
|
29
|
+
*/
|
|
30
|
+
export function writeSlashCommandDeposit(projectRoot, io = { printf: () => undefined }, policy = loadHostSlashCommandsPolicyFromProject(projectRoot)) {
|
|
31
|
+
const rootAbs = resolve(projectRoot);
|
|
32
|
+
const writtenPaths = [];
|
|
33
|
+
const removedPaths = [];
|
|
34
|
+
const preservedCustomPaths = [];
|
|
35
|
+
const depositedHosts = [];
|
|
36
|
+
const skippedHosts = [];
|
|
37
|
+
for (const hostId of listSlashEmitterHosts()) {
|
|
38
|
+
if (!isHostSlashCommandDepositEnabled(hostId, policy)) {
|
|
39
|
+
skippedHosts.push(hostId);
|
|
40
|
+
removedPaths.push(...stripManagedHostCommandFiles(rootAbs, hostId));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
depositedHosts.push(hostId);
|
|
44
|
+
const files = emitHostCommandFiles(hostId);
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
const outcome = writeHostCommandFileIfChanged(rootAbs, file);
|
|
47
|
+
if (outcome === "written")
|
|
48
|
+
writtenPaths.push(file.relativePath);
|
|
49
|
+
if (outcome === "preserved")
|
|
50
|
+
preservedCustomPaths.push(file.relativePath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (writtenPaths.length > 0) {
|
|
54
|
+
const hostSummary = depositedHosts.join(", ");
|
|
55
|
+
io.printf(`Installed Directive slash commands for hosts [${hostSummary}]: ${writtenPaths.length} file(s)\n`);
|
|
56
|
+
}
|
|
57
|
+
if (preservedCustomPaths.length > 0) {
|
|
58
|
+
io.printf(`Preserved non-managed slash command customizations: ${preservedCustomPaths.join(", ")}\n`);
|
|
59
|
+
}
|
|
60
|
+
if (removedPaths.length > 0) {
|
|
61
|
+
io.printf(`Removed Directive-managed slash commands (plan.policy.hostSlashCommands opt-out): ${removedPaths.join(", ")}\n`);
|
|
62
|
+
}
|
|
63
|
+
if (writtenPaths.length === 0 && removedPaths.length === 0) {
|
|
64
|
+
if (depositedHosts.length === 0) {
|
|
65
|
+
io.printf("Directive slash commands: all hosts opted out via plan.policy.hostSlashCommands.\n");
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
io.printf("Directive slash commands already current.\n");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
changed: writtenPaths.length + removedPaths.length > 0,
|
|
73
|
+
writtenPaths,
|
|
74
|
+
removedPaths,
|
|
75
|
+
preservedCustomPaths,
|
|
76
|
+
depositedHosts,
|
|
77
|
+
skippedHosts,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Exact repo-relative product command paths (all emitter hosts).
|
|
82
|
+
* Used by installerManagedMatchers for L8 prefer-commit staging without
|
|
83
|
+
* claiming whole host command directories (consumer custom files stay app-owned).
|
|
84
|
+
*/
|
|
85
|
+
export function slashCommandManagedExactPaths() {
|
|
86
|
+
const paths = [];
|
|
87
|
+
for (const hostId of listSlashEmitterHosts()) {
|
|
88
|
+
const dir = getHostCommandLayout(hostId).relativeDir;
|
|
89
|
+
for (const cmd of listProductCommands()) {
|
|
90
|
+
paths.push(`${dir}/${logicalIdToFilename(cmd.logicalId)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return paths;
|
|
94
|
+
}
|
|
95
|
+
function isManagedThinContent(raw, file) {
|
|
96
|
+
return raw === file.contents || isThinWrapperMarkdown(raw, file.dispatchPath);
|
|
97
|
+
}
|
|
98
|
+
function writeHostCommandFileIfChanged(projectRoot, file) {
|
|
99
|
+
const absolute = join(projectRoot, file.relativePath);
|
|
100
|
+
assertDepositContained(projectRoot, absolute);
|
|
101
|
+
if (existsSync(absolute)) {
|
|
102
|
+
let raw;
|
|
103
|
+
try {
|
|
104
|
+
raw = readFileSync(absolute, "utf8");
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Unreadable existing file: do not clobber consumer content.
|
|
108
|
+
return "preserved";
|
|
109
|
+
}
|
|
110
|
+
if (raw === file.contents)
|
|
111
|
+
return "unchanged";
|
|
112
|
+
// Ownership gate: only rewrite managed thin wrappers.
|
|
113
|
+
if (!isManagedThinContent(raw, file)) {
|
|
114
|
+
return "preserved";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const parent = dirname(absolute);
|
|
118
|
+
mkdirSync(parent, { recursive: true });
|
|
119
|
+
// Atomic replace via temp under project root (#2951).
|
|
120
|
+
const tmpName = `${basename(absolute)}.deft-${process.pid}.tmp`;
|
|
121
|
+
const temporary = join(parent, tmpName);
|
|
122
|
+
try {
|
|
123
|
+
containedWrite({
|
|
124
|
+
root: projectRoot,
|
|
125
|
+
target: temporary,
|
|
126
|
+
data: file.contents,
|
|
127
|
+
mode: "replace",
|
|
128
|
+
});
|
|
129
|
+
renameSync(temporary, absolute);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
try {
|
|
133
|
+
rmSync(temporary, { force: true });
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
/* best-effort cleanup */
|
|
137
|
+
}
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
return "written";
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* On opt-out, remove only product-set managed thin wrappers for that host.
|
|
144
|
+
* User-authored or non-thin files under the host command dir are left untouched.
|
|
145
|
+
*/
|
|
146
|
+
function stripManagedHostCommandFiles(projectRoot, hostId) {
|
|
147
|
+
const removed = [];
|
|
148
|
+
const files = emitHostCommandFiles(hostId);
|
|
149
|
+
for (const file of files) {
|
|
150
|
+
const absolute = join(projectRoot, file.relativePath);
|
|
151
|
+
assertDepositContained(projectRoot, absolute);
|
|
152
|
+
if (!existsSync(absolute))
|
|
153
|
+
continue;
|
|
154
|
+
let raw;
|
|
155
|
+
try {
|
|
156
|
+
raw = readFileSync(absolute, "utf8");
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (!isManagedThinContent(raw, file)) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
rmSync(absolute, { force: true });
|
|
166
|
+
removed.push(file.relativePath);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
/* best-effort */
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return removed;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=slash-deposit.js.map
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type ScanFlag } from "../cache/scanner.js";
|
|
2
|
+
import { type IssueComment as ShapeIssueComment } from "../umbrella-current-shape/index.js";
|
|
2
3
|
import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY } from "../xbrief-migrate/constants.js";
|
|
3
4
|
import { type ScmCallFn } from "./reconcile-issues.js";
|
|
5
|
+
/** Reference type pointing at the canonical current-shape comment permalink (#1870). */
|
|
6
|
+
export declare const CURRENT_SHAPE_REF_TYPE: "x-xbrief/current-shape";
|
|
4
7
|
export declare const INGEST_STATUSES: readonly ["proposed", "pending", "active"];
|
|
5
8
|
export type IngestStatus = (typeof INGEST_STATUSES)[number];
|
|
6
9
|
/**
|
|
@@ -21,9 +24,26 @@ export interface IssueComment {
|
|
|
21
24
|
readonly login?: string;
|
|
22
25
|
};
|
|
23
26
|
readonly created_at?: string;
|
|
27
|
+
readonly updated_at?: string;
|
|
28
|
+
readonly html_url?: string;
|
|
29
|
+
readonly author_association?: string;
|
|
24
30
|
}
|
|
25
31
|
/** Enriched on issues after `fetchIssue` when the comment thread is non-empty (#2143). */
|
|
26
32
|
export declare const ISSUE_COMMENT_THREAD_KEY: "issueCommentThread";
|
|
33
|
+
/**
|
|
34
|
+
* Map ingest/REST comment rows onto the umbrella-current-shape selector input
|
|
35
|
+
* so #1152 pass-N + #2307 maintainer provenance rules apply (#1870).
|
|
36
|
+
*/
|
|
37
|
+
export declare function mapIngestCommentsToShapeComments(comments: readonly IssueComment[]): ShapeIssueComment[];
|
|
38
|
+
/**
|
|
39
|
+
* Select the canonical current-shape comment from an ingest comment thread.
|
|
40
|
+
* Returns null when none is maintainer-authored / header-matched.
|
|
41
|
+
*/
|
|
42
|
+
export declare function selectIngestCurrentShape(comments: readonly IssueComment[]): (ShapeIssueComment & {
|
|
43
|
+
pass: number;
|
|
44
|
+
}) | null;
|
|
45
|
+
/** Count maintainer current-shape comments (lint surface for !=1). */
|
|
46
|
+
export declare function countIngestCurrentShapeComments(comments: readonly IssueComment[]): number;
|
|
27
47
|
export declare function bodyControlCharacterLabels(body: string): string[];
|
|
28
48
|
export declare function warnBodyControlCharacters(number: number, body: string): void;
|
|
29
49
|
export declare function extractPlanItems(body: string): Record<string, string>[];
|
|
@@ -7,11 +7,14 @@ import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
|
|
|
7
7
|
import { call } from "../scm/call.js";
|
|
8
8
|
import { resolveProjectRoot } from "../scope/project-context.js";
|
|
9
9
|
import { resolveProjectRepo } from "../slice/project-context.js";
|
|
10
|
+
import { countMaintainerCurrentShapeComments, mapIssueCommentEntry, RAW_ISSUE_COMMENTS_KEY, selectCurrentShapeComment, } from "../umbrella-current-shape/index.js";
|
|
10
11
|
import { slugify, TODAY } from "../vbrief-build/build.js";
|
|
11
12
|
import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
|
|
12
13
|
import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, VBRIEF_VERSION, } from "../xbrief-migrate/constants.js";
|
|
13
14
|
import { findAcHeading, parseCheckboxItems, parseListItems, sliceAcSection, stripCodeBlocks, stripFencedCodeBlocks, } from "./markdown-scanners.js";
|
|
14
15
|
import { detectRepo, extractReferencesFromVbrief, fetchOpenIssues, GITHUB_ISSUE_REF_TYPES, LIFECYCLE_FOLDERS, parseIssueNumber, } from "./reconcile-issues.js";
|
|
16
|
+
/** Reference type pointing at the canonical current-shape comment permalink (#1870). */
|
|
17
|
+
export const CURRENT_SHAPE_REF_TYPE = "x-xbrief/current-shape";
|
|
15
18
|
export const INGEST_STATUSES = ["proposed", "pending", "active"];
|
|
16
19
|
/**
|
|
17
20
|
* Thrown when the quarantine scanner hard-fails (credential-shaped content) on
|
|
@@ -36,6 +39,31 @@ export class ScannerHardFailError extends Error {
|
|
|
36
39
|
}
|
|
37
40
|
/** Enriched on issues after `fetchIssue` when the comment thread is non-empty (#2143). */
|
|
38
41
|
export const ISSUE_COMMENT_THREAD_KEY = "issueCommentThread";
|
|
42
|
+
/**
|
|
43
|
+
* Map ingest/REST comment rows onto the umbrella-current-shape selector input
|
|
44
|
+
* so #1152 pass-N + #2307 maintainer provenance rules apply (#1870).
|
|
45
|
+
*/
|
|
46
|
+
export function mapIngestCommentsToShapeComments(comments) {
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const comment of comments) {
|
|
49
|
+
const mapped = mapIssueCommentEntry(comment);
|
|
50
|
+
if (mapped !== null) {
|
|
51
|
+
out.push(mapped);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Select the canonical current-shape comment from an ingest comment thread.
|
|
58
|
+
* Returns null when none is maintainer-authored / header-matched.
|
|
59
|
+
*/
|
|
60
|
+
export function selectIngestCurrentShape(comments) {
|
|
61
|
+
return selectCurrentShapeComment(mapIngestCommentsToShapeComments(comments));
|
|
62
|
+
}
|
|
63
|
+
/** Count maintainer current-shape comments (lint surface for !=1). */
|
|
64
|
+
export function countIngestCurrentShapeComments(comments) {
|
|
65
|
+
return countMaintainerCurrentShapeComments(mapIngestCommentsToShapeComments(comments));
|
|
66
|
+
}
|
|
39
67
|
const STATUS_MAP = {
|
|
40
68
|
proposed: ["proposed", "proposed"],
|
|
41
69
|
pending: ["pending", "pending"],
|
|
@@ -326,6 +354,11 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
|
|
|
326
354
|
const bodyStr = typeof bodyRaw === "string" && bodyRaw.length > 0 ? bodyRaw : "";
|
|
327
355
|
const commentThread = issueCommentThread(issue);
|
|
328
356
|
const overviewSource = commentThread.length > 0 ? composeOverviewWithComments(bodyStr, commentThread) : bodyStr;
|
|
357
|
+
// #1870 / #1152: materialize the canonical Current shape comment as its own
|
|
358
|
+
// narrative so agents cannot plan umbrellas from the stale body alone. Prefer
|
|
359
|
+
// the same selector as `task umbrella:current-shape` (highest pass-N,
|
|
360
|
+
// maintainer-authored only — #2307).
|
|
361
|
+
const currentShape = selectIngestCurrentShape(commentThread);
|
|
329
362
|
const labelsRaw = issue.labels;
|
|
330
363
|
const labelNames = [];
|
|
331
364
|
if (Array.isArray(labelsRaw)) {
|
|
@@ -366,6 +399,10 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
|
|
|
366
399
|
}
|
|
367
400
|
narratives.Overview = scanResult.transformed_content;
|
|
368
401
|
}
|
|
402
|
+
if (currentShape !== null) {
|
|
403
|
+
// Scan shape body under the same fail-closed contract as Overview (#2306).
|
|
404
|
+
narratives.CurrentShape = scanUntrustedIngestText(number, currentShape.body);
|
|
405
|
+
}
|
|
369
406
|
if (labelNames.length > 0) {
|
|
370
407
|
narratives.Labels = labelNames.join(", ");
|
|
371
408
|
}
|
|
@@ -392,6 +429,16 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
|
|
|
392
429
|
title: `Issue #${number}: ${title}`,
|
|
393
430
|
},
|
|
394
431
|
];
|
|
432
|
+
if (currentShape !== null) {
|
|
433
|
+
const shapeUri = currentShape.htmlUrl.length > 0
|
|
434
|
+
? currentShape.htmlUrl
|
|
435
|
+
: `${url}#issuecomment-${currentShape.id}`;
|
|
436
|
+
references.push({
|
|
437
|
+
uri: shapeUri,
|
|
438
|
+
type: CURRENT_SHAPE_REF_TYPE,
|
|
439
|
+
title: `Current shape (pass-${currentShape.pass}) for #${number}`,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
395
442
|
if (overviewSource.length > 0 && repoUrl.length > 0) {
|
|
396
443
|
references.push(...extractCrossRefs(overviewSource, repoUrl, new Set([number])));
|
|
397
444
|
}
|
|
@@ -446,6 +493,12 @@ export function fetchFromCache(repo, number, options = {}) {
|
|
|
446
493
|
// the quarantine transform. When content.md is absent (e.g. a credential
|
|
447
494
|
// hard-fail deleted it), the raw body falls through and is re-scanned in
|
|
448
495
|
// buildIssueVbrief.
|
|
496
|
+
//
|
|
497
|
+
// #1870 note: content.md may also include the appended Canonical current
|
|
498
|
+
// shape section. That is intentional for agent-facing cache reads. When
|
|
499
|
+
// raw.comments is present we also restore the comment thread so
|
|
500
|
+
// narratives.CurrentShape + the current-shape reference can be materialised
|
|
501
|
+
// offline (without a live comments fetch).
|
|
449
502
|
if (result.contentPath !== null) {
|
|
450
503
|
try {
|
|
451
504
|
// #2314: content.md is `renderContent`-prefixed with a `# #<n>: <title>`
|
|
@@ -458,6 +511,11 @@ export function fetchFromCache(repo, number, options = {}) {
|
|
|
458
511
|
// fall back to the raw body (re-scanned downstream)
|
|
459
512
|
}
|
|
460
513
|
}
|
|
514
|
+
// #1870: restore comment thread from cache raw so CurrentShape survives
|
|
515
|
+
// offline / no-live-gh paths (fetchIssue still prefers live comments).
|
|
516
|
+
if (!issueCommentsAlreadyFetched(issue) && Array.isArray(issue[RAW_ISSUE_COMMENTS_KEY])) {
|
|
517
|
+
return attachIssueCommentThread(issue, issue[RAW_ISSUE_COMMENTS_KEY]);
|
|
518
|
+
}
|
|
461
519
|
return issue;
|
|
462
520
|
}
|
|
463
521
|
catch {
|