@dadado/agent-kit-cli 5.15.1 → 5.17.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.
Files changed (72) hide show
  1. package/dashboard/dashboard-data.mjs +69 -65
  2. package/dashboard/lib/guards.d.mts +5 -0
  3. package/dashboard/lib/guards.mjs +31 -0
  4. package/dashboard/lib/identity-probe.mjs +56 -0
  5. package/dashboard/lib/pipeline-cache.mjs +58 -0
  6. package/dashboard/lib/processes.mjs +71 -0
  7. package/dashboard/serve.mjs +43 -1
  8. package/dashboard/start-broadcast.mjs +2 -12
  9. package/dashboard/start.mjs +2 -13
  10. package/dist/add-HTYIFKMN.js +150 -0
  11. package/dist/chunk-2IPXQFJE.js +253 -0
  12. package/dist/chunk-2RDRMGD3.js +82 -0
  13. package/dist/chunk-2YU2H7I6.js +42 -0
  14. package/dist/chunk-4IWG5CVE.js +214 -0
  15. package/dist/chunk-5VCOWTTP.js +6 -0
  16. package/dist/chunk-77V75A7O.js +39 -0
  17. package/dist/chunk-7HDQEK6T.js +26 -0
  18. package/dist/chunk-AW74INUW.js +1456 -0
  19. package/dist/chunk-B73KQ5KX.js +972 -0
  20. package/dist/chunk-CBCW5R5J.js +484 -0
  21. package/dist/chunk-DE3LQFSJ.js +18 -0
  22. package/dist/chunk-DQA434Y4.js +106 -0
  23. package/dist/chunk-F4VMYR7U.js +318 -0
  24. package/dist/chunk-F6C3GFKV.js +57 -0
  25. package/dist/chunk-HFUH3HVS.js +282 -0
  26. package/dist/chunk-HS7LVVLO.js +74 -0
  27. package/dist/chunk-IHROBTNM.js +135 -0
  28. package/dist/chunk-KD2MYBB6.js +213 -0
  29. package/dist/chunk-MAPOQEA2.js +43 -0
  30. package/dist/chunk-N3HTJVBR.js +341 -0
  31. package/dist/chunk-NQ7IQ5XT.js +26 -0
  32. package/dist/chunk-OUQZC744.js +1315 -0
  33. package/dist/chunk-PCCQUWE5.js +817 -0
  34. package/dist/chunk-PNFF6OU3.js +273 -0
  35. package/dist/chunk-PNG7LO2F.js +53 -0
  36. package/dist/chunk-QTON2GQX.js +203 -0
  37. package/dist/chunk-S6GMFDKV.js +50 -0
  38. package/dist/chunk-SMGFN3Z4.js +503 -0
  39. package/dist/chunk-STGFBAYT.js +641 -0
  40. package/dist/chunk-TFZ2JUXH.js +1081 -0
  41. package/dist/chunk-VY7MTPJ2.js +205 -0
  42. package/dist/chunk-W24CMQ32.js +525 -0
  43. package/dist/chunk-WW3YZKHW.js +165 -0
  44. package/dist/chunk-ZB43YMHW.js +20 -0
  45. package/dist/chunk-ZCTSUQRR.js +874 -0
  46. package/dist/chunk-ZXKZBTXE.js +17 -0
  47. package/dist/contribute-C6AW2P6J.js +367 -0
  48. package/dist/cursor-awareness-KTL2RGKD.js +77 -0
  49. package/dist/dashboard-YSNUGBFY.js +21 -0
  50. package/dist/dashboard-broadcast-M4CMW7RX.js +119 -0
  51. package/dist/diff-VHNR62SI.js +74 -0
  52. package/dist/doctor-PET2HRRJ.js +236 -0
  53. package/dist/dogfood-KKFVRWT6.js +366 -0
  54. package/dist/guard-EHXF76ID.js +530 -0
  55. package/dist/handoff-3UI6VKVG.js +302 -0
  56. package/dist/hook-DYIMKJYL.js +1074 -0
  57. package/dist/index.js +62 -14068
  58. package/dist/init-Q4LBM6KT.js +127 -0
  59. package/dist/install-RHOS3OYZ.js +34 -0
  60. package/dist/mission-control-LJACEVVN.js +89 -0
  61. package/dist/monitors-QV2IS3MP.js +176 -0
  62. package/dist/plan-index-JMC7Z4DW.js +44 -0
  63. package/dist/public-inbound-radar-WP3XF7GS.js +68 -0
  64. package/dist/remote-issues-SZ4HDQL5.js +44 -0
  65. package/dist/run-I3BFGULT.js +243 -0
  66. package/dist/run-plan-FO7CL4M3.js +118 -0
  67. package/dist/scan-NI2SWJ2E.js +33 -0
  68. package/dist/setup-global-M42GEODI.js +340 -0
  69. package/dist/status-XPVQVQMX.js +150 -0
  70. package/dist/update-FJ2EBZVO.js +283 -0
  71. package/dist/validate-V2CW7J4B.js +201 -0
  72. package/package.json +3 -3
@@ -0,0 +1,236 @@
1
+ import {
2
+ assessHooksHealth,
3
+ executeSafeReadinessFixes,
4
+ refreshRepositoryProfile
5
+ } from "./chunk-STGFBAYT.js";
6
+ import {
7
+ writeReadinessSnapshot
8
+ } from "./chunk-ZXKZBTXE.js";
9
+ import "./chunk-W24CMQ32.js";
10
+ import "./chunk-MAPOQEA2.js";
11
+ import {
12
+ withCliProgress
13
+ } from "./chunk-QTON2GQX.js";
14
+ import {
15
+ detectWorkspaceParent,
16
+ workspaceParentAdvisoryLines
17
+ } from "./chunk-S6GMFDKV.js";
18
+ import {
19
+ createReadinessReport
20
+ } from "./chunk-PNFF6OU3.js";
21
+ import {
22
+ runScanner
23
+ } from "./chunk-ZCTSUQRR.js";
24
+ import "./chunk-F6C3GFKV.js";
25
+ import {
26
+ logger
27
+ } from "./chunk-ZB43YMHW.js";
28
+ import {
29
+ assessEnvironment
30
+ } from "./chunk-4IWG5CVE.js";
31
+ import {
32
+ warnIfRunningCliBehindNpm
33
+ } from "./chunk-SMGFN3Z4.js";
34
+ import "./chunk-NQ7IQ5XT.js";
35
+ import "./chunk-2RDRMGD3.js";
36
+ import "./chunk-N3HTJVBR.js";
37
+ import "./chunk-AW74INUW.js";
38
+ import "./chunk-5VCOWTTP.js";
39
+ import {
40
+ KIT_VERSION
41
+ } from "./chunk-7HDQEK6T.js";
42
+ import {
43
+ readJson
44
+ } from "./chunk-77V75A7O.js";
45
+
46
+ // src/commands/doctor.ts
47
+ import path from "path";
48
+ import { defineCommand } from "citty";
49
+ async function readDeferredEssentials(rootDir, report) {
50
+ const config = await readJson(
51
+ path.join(rootDir, ".cursor", "context", "config.json")
52
+ );
53
+ const deferredItems = config?.onboarding?.deferredItems;
54
+ if (!Array.isArray(deferredItems)) return [];
55
+ const essentialIds = new Set(
56
+ report.pillars.flatMap((pillar) => pillar.checks).filter((check) => check.essential).map((check) => check.id)
57
+ );
58
+ return deferredItems.filter(
59
+ (item) => typeof item?.checkId === "string" && typeof item?.reason === "string" && essentialIds.has(item.checkId)
60
+ ).map((item) => ({ checkId: item.checkId, reason: item.reason }));
61
+ }
62
+ async function runDoctor(cwd, options = {}) {
63
+ const rootDir = path.resolve(cwd);
64
+ const hooks = await assessHooksHealth(rootDir);
65
+ const workspaceParent = await detectWorkspaceParent(rootDir);
66
+ if (workspaceParent) {
67
+ const scan2 = await runScanner(rootDir);
68
+ const report2 = createReadinessReport(scan2, {
69
+ generatorVersion: KIT_VERSION,
70
+ generatedAt: options.generatedAt
71
+ });
72
+ return {
73
+ report: report2,
74
+ safeChanges: [],
75
+ hooks,
76
+ env: await assessEnvironment(),
77
+ deferredEssentials: [],
78
+ workspaceParent
79
+ };
80
+ }
81
+ const env = await assessEnvironment();
82
+ if (options.fixSafe) {
83
+ const execution = await executeSafeReadinessFixes(rootDir, {
84
+ generatorVersion: KIT_VERSION,
85
+ generatedAt: options.generatedAt
86
+ });
87
+ await writeReadinessSnapshot(rootDir, execution.after);
88
+ return {
89
+ report: execution.after,
90
+ safeChanges: execution.changes,
91
+ hooks,
92
+ env,
93
+ deferredEssentials: await readDeferredEssentials(rootDir, execution.after)
94
+ };
95
+ }
96
+ if (options.refreshProfile) {
97
+ const refresh = await refreshRepositoryProfile(rootDir, {
98
+ generatorVersion: KIT_VERSION,
99
+ generatedAt: options.generatedAt
100
+ });
101
+ const scan2 = await runScanner(rootDir);
102
+ const report2 = createReadinessReport(scan2, {
103
+ generatorVersion: KIT_VERSION,
104
+ generatedAt: options.generatedAt
105
+ });
106
+ await writeReadinessSnapshot(rootDir, report2);
107
+ return {
108
+ report: report2,
109
+ safeChanges: [],
110
+ hooks,
111
+ env,
112
+ profileRefreshed: refresh.changed,
113
+ deferredEssentials: await readDeferredEssentials(rootDir, report2)
114
+ };
115
+ }
116
+ const scan = await runScanner(rootDir);
117
+ const report = createReadinessReport(scan, {
118
+ generatorVersion: KIT_VERSION,
119
+ generatedAt: options.generatedAt
120
+ });
121
+ await writeReadinessSnapshot(rootDir, report);
122
+ return {
123
+ report,
124
+ safeChanges: [],
125
+ hooks,
126
+ env,
127
+ deferredEssentials: await readDeferredEssentials(rootDir, report)
128
+ };
129
+ }
130
+ function printDoctorSummary(result, rootDir) {
131
+ if (result.workspaceParent) {
132
+ for (const line of workspaceParentAdvisoryLines(rootDir, result.workspaceParent, "doctor")) {
133
+ console.log(line);
134
+ }
135
+ return;
136
+ }
137
+ const { summary, pendingActions } = result.report;
138
+ const fixed = result.safeChanges.filter((change) => change.status === "applied").length;
139
+ const nextAction = pendingActions[0];
140
+ console.log("Repository readiness");
141
+ console.log(
142
+ ` ready: ${summary.ready}, choices: ${summary.needs_choice}, manual: ${summary.manual}, blocked: ${summary.blocked}`
143
+ );
144
+ console.log(` safe fixes applied: ${fixed}`);
145
+ console.log(` pending actions: ${pendingActions.length}`);
146
+ if (result.profileRefreshed !== void 0) {
147
+ console.log(
148
+ ` profile refreshed: ${result.profileRefreshed ? "yes (facts changed)" : "no (already current)"}`
149
+ );
150
+ }
151
+ for (const item of result.deferredEssentials) {
152
+ console.log(` Deferred essential: ${item.checkId} \u2014 ${item.reason}`);
153
+ }
154
+ console.log(`hooks: ${result.hooks.status}`);
155
+ if (result.hooks.reasons.length > 0) {
156
+ for (const reason of result.hooks.reasons.slice(0, 5)) {
157
+ console.log(` - ${reason}`);
158
+ }
159
+ }
160
+ if (result.hooks.advisories.length > 0) {
161
+ console.log("hooks advisories (soft; install via cp, see git-hooks/README.md):");
162
+ for (const tip of result.hooks.advisories.slice(0, 5)) {
163
+ console.log(` - ${tip}`);
164
+ }
165
+ }
166
+ console.log("environment:");
167
+ const pathLabel = result.env.binOnPath ? `ok (v${result.env.binVersion ?? "unknown"} at ${result.env.binPath})` : "MISSING";
168
+ console.log(` - bin on PATH (agent-kit): ${pathLabel}`);
169
+ console.log(
170
+ ` - npm prefix writable: ${result.env.npmPrefixWritable ? "ok" : "BLOCKED"}${result.env.npmPrefix.prefix ? ` (${result.env.npmPrefix.prefix})` : ""}`
171
+ );
172
+ if (!result.env.npmPrefixWritable && result.env.npmPrefix.reason) {
173
+ console.log(` - ${result.env.npmPrefix.reason}`);
174
+ }
175
+ console.log(
176
+ ` - node version >= 20: ${result.env.nodeVersionOk ? "ok" : "TOO OLD"} (${result.env.nodeVersion})`
177
+ );
178
+ console.log(
179
+ ` - shell profile: ${result.env.shellProfile ?? "not detected (zsh/bash only)"}${result.env.shell ? ` (shell: ${result.env.shell})` : ""}`
180
+ );
181
+ if (process.env.ALLOW_MAIN_PUSH === "1") {
182
+ console.log("\u26A0\uFE0F WARNING: ALLOW_MAIN_PUSH=1 is set in environment");
183
+ console.log(" This disables main-push protection for agent Shell commands.");
184
+ console.log(" Consider unsetting it: unset ALLOW_MAIN_PUSH");
185
+ }
186
+ console.log(
187
+ nextAction ? `Next: ${nextAction.recommendation}` : "Next: repository readiness checks are complete"
188
+ );
189
+ }
190
+ var doctorCommand = defineCommand({
191
+ meta: {
192
+ name: "doctor",
193
+ description: "Diagnose repository readiness; optional --fix-safe local repairs."
194
+ },
195
+ args: {
196
+ cwd: {
197
+ type: "string",
198
+ default: process.cwd()
199
+ },
200
+ json: {
201
+ type: "boolean",
202
+ description: "Print deterministic machine-readable JSON without status messages",
203
+ default: false
204
+ },
205
+ "fix-safe": {
206
+ type: "boolean",
207
+ description: "Apply only local, reversible, merge-safe readiness fixes",
208
+ default: false
209
+ },
210
+ "refresh-profile": {
211
+ type: "boolean",
212
+ description: "Reconcile .cursor/agent-kit.config.json with current scanner facts: fresh values win on shared keys, unrecognized existing keys are preserved",
213
+ default: false
214
+ }
215
+ },
216
+ async run({ args }) {
217
+ if (!args.json) {
218
+ await warnIfRunningCliBehindNpm(args.cwd, { warn: (message) => logger.warn(message) });
219
+ }
220
+ const run = () => runDoctor(args.cwd, {
221
+ fixSafe: args["fix-safe"],
222
+ refreshProfile: args["refresh-profile"]
223
+ });
224
+ const result = args.json ? await run() : await withCliProgress("doctor", run);
225
+ if (args.json) {
226
+ console.log(JSON.stringify(result, null, 2));
227
+ return;
228
+ }
229
+ printDoctorSummary(result, path.resolve(args.cwd));
230
+ }
231
+ });
232
+ export {
233
+ doctorCommand,
234
+ printDoctorSummary,
235
+ runDoctor
236
+ };
@@ -0,0 +1,366 @@
1
+ import {
2
+ REMOTE_ISSUE_TITLE_PREFIX,
3
+ parseRemoteUrl
4
+ } from "./chunk-VY7MTPJ2.js";
5
+ import {
6
+ PUBLIC_INBOUND_REPO
7
+ } from "./chunk-2IPXQFJE.js";
8
+ import {
9
+ logger
10
+ } from "./chunk-ZB43YMHW.js";
11
+ import "./chunk-NQ7IQ5XT.js";
12
+ import "./chunk-77V75A7O.js";
13
+
14
+ // src/commands/dogfood.ts
15
+ import { defineCommand } from "citty";
16
+
17
+ // src/lifecycle/dogfood-file.ts
18
+ import { execFile } from "child_process";
19
+ import { access, mkdir, readFile, writeFile } from "fs/promises";
20
+ import path from "path";
21
+ import { promisify } from "util";
22
+ var execFileAsync = promisify(execFile);
23
+ var TRANSIENT_PHRASES = [/\bas I mentioned\b/gi, /\bdear user\b/gi, /\bconforme falamos\b/gi];
24
+ function hygieneStrip(input) {
25
+ const redacted = /* @__PURE__ */ new Set();
26
+ let text = input;
27
+ const apply = (name, pattern, replacement) => {
28
+ text = text.replace(pattern, () => {
29
+ redacted.add(name);
30
+ return replacement;
31
+ });
32
+ };
33
+ apply(
34
+ "secret",
35
+ /\b(?:gh[pousr]_[A-Za-z0-9]{20,}|glpat-[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g,
36
+ "[redacted-secret]"
37
+ );
38
+ apply(
39
+ "secret",
40
+ /\b(?:api[_-]?key|token|secret|password|passwd)\s*[:=]\s*\S+/gi,
41
+ "[redacted-secret]"
42
+ );
43
+ apply("secret", /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/g, "[redacted-secret]");
44
+ apply("email", /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g, "[redacted-email]");
45
+ apply("path", /(?:\/Users|\/home)\/[^\s/]+(?:\/[^\s`'")]*)?/g, "[redacted-path]");
46
+ for (const phrase of TRANSIENT_PHRASES) apply("transient", phrase, "");
47
+ text = text.replace(/[ \t]{2,}/g, " ");
48
+ const needsAnonymization = ["secret", "email", "path"].some((kind) => redacted.has(kind));
49
+ return { text: text.trim(), redacted: [...redacted], needsAnonymization };
50
+ }
51
+ function normalizeTopic(topic) {
52
+ const normalized = topic.toLowerCase().replace(/\d{4}[-_]?\d{2}[-_]?\d{2}\s*$/, "").replace(/[\s_-]+/g, "_").replace(/[^a-z0-9_]/g, "").replace(/^_+|_+$/g, "").slice(0, 40).replace(/_+$/g, "");
53
+ return normalized || "note";
54
+ }
55
+ function dogfoodFilename(topic, date) {
56
+ const stamp = date.toISOString().slice(0, 10).replaceAll("-", "_");
57
+ return `cursor_${normalizeTopic(topic)}_${stamp}.md`;
58
+ }
59
+ function renderDogfoodNote(input) {
60
+ const tags = (input.tags ?? []).map((tag) => tag.trim().toLowerCase()).filter(Boolean);
61
+ const lines = [
62
+ `# Dogfood: ${input.topic}`,
63
+ "",
64
+ `- **Date:** ${input.date.toISOString().slice(0, 10)}`,
65
+ `- **Lane:** ${input.lane}`,
66
+ `- **Source:** ${input.source ?? "explicit dogfood args"}`
67
+ ];
68
+ if (input.needsAnonymization) lines.push("- **Flag:** needs-anonymization");
69
+ lines.push(
70
+ "",
71
+ "## Observation",
72
+ "",
73
+ input.observation || "(no description)",
74
+ "",
75
+ "## Impact",
76
+ "",
77
+ input.impact || "(to be assessed at triage)",
78
+ "",
79
+ "## Triage (initial)",
80
+ "",
81
+ "- Fix now / Park / Ignore",
82
+ `- **Tags:** ${tags.join(", ")}`,
83
+ ""
84
+ );
85
+ return lines.join("\n");
86
+ }
87
+ async function exists(target) {
88
+ try {
89
+ await access(target);
90
+ return true;
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+ async function defaultGetRemoteUrl(cwd, name) {
96
+ try {
97
+ const { stdout } = await execFileAsync("git", ["remote", "get-url", name], {
98
+ cwd,
99
+ encoding: "utf8",
100
+ timeout: 5e3
101
+ });
102
+ return stdout.trim() || null;
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+ async function defaultRunCli(bin, args, options) {
108
+ const { stdout } = await execFileAsync(bin, args, {
109
+ cwd: options?.cwd,
110
+ encoding: "utf8",
111
+ maxBuffer: 4 * 1024 * 1024,
112
+ timeout: 3e4
113
+ });
114
+ return stdout;
115
+ }
116
+ async function detectDogfoodLane(root, deps = {}) {
117
+ const getRemoteUrl = deps.getRemoteUrl ?? defaultGetRemoteUrl;
118
+ const origin = await getRemoteUrl(root, "origin");
119
+ if (origin && /agent-kit-dev/i.test(origin)) return "factory";
120
+ if (await exists(path.join(root, "dogfood", "README.md"))) return "factory";
121
+ if (await exists(path.join(root, ".cursor", "agent-kit.json"))) return "consumer";
122
+ return null;
123
+ }
124
+ function inboxDir(root, lane) {
125
+ return lane === "factory" ? path.join(root, "dogfood") : path.join(root, ".cursor", "dogfood");
126
+ }
127
+ var UNPROCESSED_HEADING = /^(#{2,3})\s+Unprocessed Files\s*$/m;
128
+ var NEW_INDEX = "# Dogfood inbox\n\n### Unprocessed Files\n\n### Processed Files\n";
129
+ function appendUnprocessedEntry(readme, entry) {
130
+ const match = UNPROCESSED_HEADING.exec(readme);
131
+ if (!match) {
132
+ const base = readme.trimEnd();
133
+ return `${base}${base ? "\n\n" : ""}### Unprocessed Files
134
+
135
+ ${entry}
136
+ `;
137
+ }
138
+ const level = (match[1] ?? "###").length;
139
+ const start = match.index + match[0].length;
140
+ const rest = readme.slice(start);
141
+ const next = new RegExp(`^#{1,${level}}\\s`, "m").exec(rest);
142
+ const sectionEnd = next ? start + next.index : readme.length;
143
+ const section = readme.slice(start, sectionEnd).replace(/\s+$/, "");
144
+ const tail = readme.slice(sectionEnd);
145
+ return `${readme.slice(0, start)}${section}
146
+ ${entry}
147
+ ${tail ? `
148
+ ${tail}` : ""}`;
149
+ }
150
+ async function addDogfoodNote(root, input, deps = {}) {
151
+ const lane = await detectDogfoodLane(root, deps);
152
+ if (!lane) return { status: "unknown-lane" };
153
+ const now = deps.now?.() ?? /* @__PURE__ */ new Date();
154
+ const topic = hygieneStrip(input.topic);
155
+ const observation = hygieneStrip(input.summary ?? input.topic);
156
+ const impact = hygieneStrip(input.impact ?? "");
157
+ const redacted = [.../* @__PURE__ */ new Set([...topic.redacted, ...observation.redacted, ...impact.redacted])];
158
+ const needsAnonymization = topic.needsAnonymization || observation.needsAnonymization || impact.needsAnonymization;
159
+ const dir = inboxDir(root, lane);
160
+ const filename = dogfoodFilename(topic.text || input.topic, now);
161
+ const file = path.join(dir, filename);
162
+ if (await exists(file)) return { status: "exists", lane, file };
163
+ await mkdir(dir, { recursive: true });
164
+ await writeFile(
165
+ file,
166
+ renderDogfoodNote({
167
+ topic: topic.text || input.topic,
168
+ lane,
169
+ observation: observation.text,
170
+ impact: impact.text || void 0,
171
+ tags: input.tags,
172
+ source: input.source,
173
+ date: now,
174
+ needsAnonymization
175
+ }),
176
+ "utf8"
177
+ );
178
+ const indexFile = path.join(dir, "README.md");
179
+ const oneLine = observation.text.split(/\r?\n/)[0]?.slice(0, 200) ?? "";
180
+ const entry = `- \`${filename}\` - ${oneLine || topic.text} (captured ${now.toISOString().slice(0, 10)})${needsAnonymization ? " [needs-anonymization]" : ""}`;
181
+ const current = await exists(indexFile) ? await readFile(indexFile, "utf8") : NEW_INDEX;
182
+ await writeFile(indexFile, appendUnprocessedEntry(current, entry), "utf8");
183
+ return {
184
+ status: "filed",
185
+ lane,
186
+ file,
187
+ relativeFile: path.relative(root, file),
188
+ indexFile,
189
+ needsAnonymization,
190
+ redacted
191
+ };
192
+ }
193
+ function withMarker(title) {
194
+ const trimmed = title.trim();
195
+ return trimmed.toLowerCase().startsWith(REMOTE_ISSUE_TITLE_PREFIX.toLowerCase()) ? trimmed : `${REMOTE_ISSUE_TITLE_PREFIX} ${trimmed}`;
196
+ }
197
+ function classifyCliFailure(error) {
198
+ const err = error;
199
+ const text = `${String(err?.message ?? "")} ${String(err?.stderr ?? "")}`;
200
+ if (err?.code === "ENOENT" || /ENOENT|command not found/i.test(text))
201
+ return "skipped-cli-missing";
202
+ if (/auth|login|401|403|token|credential/i.test(text)) return "skipped-auth";
203
+ return "skipped-error";
204
+ }
205
+ function extractUrl(stdout) {
206
+ const trimmed = stdout.trim();
207
+ if (!trimmed) return null;
208
+ try {
209
+ const parsed = JSON.parse(trimmed);
210
+ const url = parsed.web_url ?? parsed.url;
211
+ if (typeof url === "string" && url) return url;
212
+ } catch {
213
+ }
214
+ return /https?:\/\/\S+/.exec(trimmed)?.[0] ?? null;
215
+ }
216
+ async function fileDogfoodIssue(root, input, deps = {}) {
217
+ const getRemoteUrl = deps.getRemoteUrl ?? defaultGetRemoteUrl;
218
+ const runCli = deps.runCli ?? defaultRunCli;
219
+ const title = hygieneStrip(withMarker(input.title));
220
+ const body = hygieneStrip(input.body);
221
+ const redacted = [.../* @__PURE__ */ new Set([...title.redacted, ...body.redacted])];
222
+ const base = {
223
+ visibility: input.visibility,
224
+ provider: null,
225
+ repo: null,
226
+ title: title.text,
227
+ confidential: false,
228
+ url: null,
229
+ redacted
230
+ };
231
+ const stop = (status, message, extra = {}) => ({ ...base, status, message, ...extra });
232
+ const lane = await detectDogfoodLane(root, deps);
233
+ if (!lane)
234
+ return stop("refused-unknown-lane", "Lane unknown: not the factory nor a kit install.");
235
+ let target;
236
+ if (input.visibility === "public") {
237
+ if (lane !== "factory") {
238
+ return stop("refused-consumer-public", "Consumers never file upstream; use a private issue.");
239
+ }
240
+ if (!input.approvedPublic) {
241
+ return stop(
242
+ "refused-not-approved",
243
+ "Public issues need explicit operator approval (--approve-public)."
244
+ );
245
+ }
246
+ if (title.needsAnonymization || body.needsAnonymization) {
247
+ return stop(
248
+ "refused-hygiene",
249
+ `Hygiene strip removed ${redacted.join(", ")}; anonymize the text and retry.`
250
+ );
251
+ }
252
+ target = `https://github.com/${PUBLIC_INBOUND_REPO}.git`;
253
+ } else {
254
+ target = await getRemoteUrl(root, "origin");
255
+ }
256
+ const remote = target ? parseRemoteUrl(target) : null;
257
+ if (!remote)
258
+ return stop("skipped-no-remote", `No ${input.visibility} remote to file the issue in.`);
259
+ const found = { provider: remote.provider, repo: remote.repo };
260
+ const finalTitle = title.text;
261
+ const finalBody = input.visibility === "public" ? body.text : input.body.trim();
262
+ try {
263
+ if (remote.provider === "gitlab") {
264
+ const args = ["api", "--method", "POST"];
265
+ if (remote.host !== "gitlab.com") args.push("--hostname", remote.host);
266
+ args.push(
267
+ `projects/${encodeURIComponent(remote.repo)}/issues`,
268
+ "-f",
269
+ `title=${finalTitle}`,
270
+ "-f",
271
+ `description=${finalBody}`,
272
+ "-f",
273
+ `confidential=${input.visibility === "private" ? "true" : "false"}`
274
+ );
275
+ const url2 = extractUrl(await runCli("glab", args, { cwd: root }));
276
+ return stop("filed", `Filed ${input.visibility} GitLab issue in ${remote.repo}.`, {
277
+ ...found,
278
+ confidential: input.visibility === "private",
279
+ url: url2
280
+ });
281
+ }
282
+ const url = extractUrl(
283
+ await runCli(
284
+ "gh",
285
+ ["issue", "create", "--repo", remote.repo, "--title", finalTitle, "--body", finalBody],
286
+ { cwd: root }
287
+ )
288
+ );
289
+ return stop("filed", `Filed ${input.visibility} GitHub issue in ${remote.repo}.`, {
290
+ ...found,
291
+ url
292
+ });
293
+ } catch (error) {
294
+ const status = classifyCliFailure(error);
295
+ return stop(status, `Issue not filed (${status}); the local note stays the record.`, found);
296
+ }
297
+ }
298
+
299
+ // src/commands/dogfood.ts
300
+ var dogfoodCommand = defineCommand({
301
+ meta: {
302
+ name: "dogfood",
303
+ description: "File dogfood findings headlessly: `add` writes the note + Unprocessed index; `file-issue` opens a private or public issue (GitHub/GitLab)."
304
+ },
305
+ subCommands: {
306
+ add: defineCommand({
307
+ meta: {
308
+ name: "add",
309
+ description: "Write the dogfood template file and append the Unprocessed index for the detected lane (factory dogfood/, consumer .cursor/dogfood/)."
310
+ },
311
+ args: {
312
+ topic: { type: "positional", description: "Short topic", required: true },
313
+ summary: { type: "string", description: "Observation (hygiene-stripped)" },
314
+ impact: { type: "string", description: "Impact on the kit or operator" },
315
+ tags: { type: "string", description: "Comma-separated lowercase tags" },
316
+ cwd: { type: "string", default: process.cwd() }
317
+ },
318
+ async run({ args }) {
319
+ const result = await addDogfoodNote(String(args.cwd), {
320
+ topic: String(args.topic),
321
+ summary: args.summary,
322
+ impact: args.impact,
323
+ tags: args.tags?.split(",").map((tag) => tag.trim()),
324
+ source: "agent-kit dogfood add"
325
+ });
326
+ console.log(JSON.stringify(result, null, 2));
327
+ if (result.status !== "filed") process.exitCode = 1;
328
+ }
329
+ }),
330
+ "file-issue": defineCommand({
331
+ meta: {
332
+ name: "file-issue",
333
+ description: "File an issue with the [Dogfood] marker. private: own origin (GitLab confidential). public: factory only, needs --approve-public and a clean hygiene strip."
334
+ },
335
+ args: {
336
+ visibility: { type: "string", description: "private | public", required: true },
337
+ title: { type: "string", required: true },
338
+ body: { type: "string", required: true },
339
+ "approve-public": {
340
+ type: "boolean",
341
+ description: "Operator approved this public issue (Ask provenance recorded in HANDOFF)",
342
+ default: false
343
+ },
344
+ cwd: { type: "string", default: process.cwd() }
345
+ },
346
+ async run({ args }) {
347
+ if (args.visibility !== "private" && args.visibility !== "public") {
348
+ logger.error("--visibility must be private or public");
349
+ process.exitCode = 1;
350
+ return;
351
+ }
352
+ const result = await fileDogfoodIssue(String(args.cwd), {
353
+ visibility: args.visibility,
354
+ title: String(args.title),
355
+ body: String(args.body),
356
+ approvedPublic: args["approve-public"] === true
357
+ });
358
+ console.log(JSON.stringify(result, null, 2));
359
+ if (result.status !== "filed") process.exitCode = 1;
360
+ }
361
+ })
362
+ }
363
+ });
364
+ export {
365
+ dogfoodCommand
366
+ };