codiedev 0.9.65 → 0.9.67
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/captureSignals.d.ts +7 -0
- package/dist/captureSignals.js +23 -2
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +14 -35
- package/dist/commands/doctor.js +0 -34
- package/dist/commands/pull.js +8 -3
- package/dist/commands/push.js +3 -1
- package/dist/mcp.js +5 -339
- package/dist/skillMarker.d.ts +6 -0
- package/dist/skillMarker.js +37 -0
- package/dist/subagents.js +7 -0
- package/dist/utils.d.ts +0 -4
- package/dist/utils.js +0 -6
- package/package.json +1 -1
- package/dist/commands/promote.d.ts +0 -1
- package/dist/commands/promote.js +0 -53
- package/dist/commands/react.d.ts +0 -2
- package/dist/commands/react.js +0 -40
- package/dist/commands/reverseTicket.d.ts +0 -2
- package/dist/commands/reverseTicket.js +0 -477
- package/dist/prUrl.d.ts +0 -9
- package/dist/prUrl.js +0 -33
package/dist/mcp.js
CHANGED
|
@@ -45,6 +45,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
45
45
|
})();
|
|
46
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
47
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
48
|
+
const skillMarker_1 = require("./skillMarker");
|
|
48
49
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
49
50
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
50
51
|
const fs = __importStar(require("fs"));
|
|
@@ -237,29 +238,6 @@ const TOOLS = [
|
|
|
237
238
|
required: ["body"],
|
|
238
239
|
},
|
|
239
240
|
},
|
|
240
|
-
{
|
|
241
|
-
name: "codiedev_promote",
|
|
242
|
-
description: "Promote an auto-extracted Codiedev artifact (spec/bugfix/decision " +
|
|
243
|
-
"produced by the session-capture pipeline) to an authored artifact " +
|
|
244
|
-
"so teammates can collaborate on it. Use when the user says 'publish " +
|
|
245
|
-
"the extracted spec', 'promote this', or 'make the auto-extracted X " +
|
|
246
|
-
"shareable'.",
|
|
247
|
-
inputSchema: {
|
|
248
|
-
type: "object",
|
|
249
|
-
properties: {
|
|
250
|
-
artifactId: {
|
|
251
|
-
type: "string",
|
|
252
|
-
description: "The extracted artifact's id from Codiedev.",
|
|
253
|
-
},
|
|
254
|
-
keyOverride: {
|
|
255
|
-
type: "string",
|
|
256
|
-
description: "Optional filename key for the promoted artifact. If omitted, " +
|
|
257
|
-
"one is generated from the artifact's type + session id.",
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
required: ["artifactId"],
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
241
|
{
|
|
264
242
|
name: "codiedev_share_with",
|
|
265
243
|
description: "Grant a teammate persistent read (or edit) access to an artifact " +
|
|
@@ -306,27 +284,6 @@ const TOOLS = [
|
|
|
306
284
|
required: ["filename", "to"],
|
|
307
285
|
},
|
|
308
286
|
},
|
|
309
|
-
{
|
|
310
|
-
name: "codiedev_react",
|
|
311
|
-
description: "React to a feed post with an emoji. Use when the user says 'react " +
|
|
312
|
-
"to that post with X', 'mark as used', or 'upvote'. The 🛠 " +
|
|
313
|
-
"reaction is the signal for 'I used this' and bumps the linked " +
|
|
314
|
-
"artifact's reuse count. Pass the post id (get it from " +
|
|
315
|
-
"codiedev_get_library or a previous feed response).",
|
|
316
|
-
inputSchema: {
|
|
317
|
-
type: "object",
|
|
318
|
-
properties: {
|
|
319
|
-
postId: { type: "string", description: "Feed post id." },
|
|
320
|
-
emoji: {
|
|
321
|
-
type: "string",
|
|
322
|
-
enum: ["👍", "🔥", "💡", "🛠", "👑"],
|
|
323
|
-
description: "Reaction emoji. 🛠 = 'I used this' (counts heaviest toward " +
|
|
324
|
-
"the author's score and the artifact's reuse count).",
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
required: ["postId", "emoji"],
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
287
|
{
|
|
331
288
|
name: "codiedev_search",
|
|
332
289
|
description: "Search the team's artifact library by natural-language query. Use " +
|
|
@@ -413,41 +370,6 @@ const TOOLS = [
|
|
|
413
370
|
},
|
|
414
371
|
},
|
|
415
372
|
},
|
|
416
|
-
{
|
|
417
|
-
name: "codiedev_reverse_ticket",
|
|
418
|
-
description: "Generate a Jira-ready reverse-ticket draft from the work the user has " +
|
|
419
|
-
"done in their CURRENT git branch — mid-session, before any PR exists. " +
|
|
420
|
-
"Reads local git state (current branch, divergence from main, diff, " +
|
|
421
|
-
"commits, files changed) and runs it through the codiedev reverse-" +
|
|
422
|
-
"ticket writer. The draft lands in the user's portal at " +
|
|
423
|
-
"/portal/agentic-ticketing where they can edit the title, description, " +
|
|
424
|
-
"and scope, then push to Jira. Use when the user asks to 'create a " +
|
|
425
|
-
"reverse ticket', 'draft a Jira ticket from this work', 'write up what " +
|
|
426
|
-
"I've done in this branch', or runs `codiedev reverse-ticket` directly. " +
|
|
427
|
-
"The webhook on PR-open later dedupes against this draft so the user " +
|
|
428
|
-
"never gets a double ticket. The draft is intentionally not auto-pushed " +
|
|
429
|
-
"to Jira — trust comes from the user reviewing in the portal first.",
|
|
430
|
-
inputSchema: {
|
|
431
|
-
type: "object",
|
|
432
|
-
properties: {
|
|
433
|
-
title: {
|
|
434
|
-
type: "string",
|
|
435
|
-
description: "Optional working title for the ticket. Defaults to the most " +
|
|
436
|
-
"recent commit subject on the branch.",
|
|
437
|
-
},
|
|
438
|
-
base: {
|
|
439
|
-
type: "string",
|
|
440
|
-
description: "Optional base ref to diff against. Defaults to 'origin/main', " +
|
|
441
|
-
"falls back to 'origin/master'.",
|
|
442
|
-
},
|
|
443
|
-
forcedArtifactKey: {
|
|
444
|
-
type: "string",
|
|
445
|
-
description: "Optional — force the writer to link to a specific artifact key " +
|
|
446
|
-
"for context (e.g. 'spec-auth.md').",
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
|
-
},
|
|
450
|
-
},
|
|
451
373
|
];
|
|
452
374
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
453
375
|
// Server
|
|
@@ -538,22 +460,16 @@ async function dispatchTool(name, args, config) {
|
|
|
538
460
|
return await handleInbox(args, config);
|
|
539
461
|
case "codiedev_note":
|
|
540
462
|
return await handleNote(args, config);
|
|
541
|
-
case "codiedev_promote":
|
|
542
|
-
return await handlePromote(args, config);
|
|
543
463
|
case "codiedev_share_with":
|
|
544
464
|
return await handleShareWith(args, config);
|
|
545
465
|
case "codiedev_send_to":
|
|
546
466
|
return await handleSendTo(args, config);
|
|
547
|
-
case "codiedev_react":
|
|
548
|
-
return await handleReact(args, config);
|
|
549
467
|
case "codiedev_ask":
|
|
550
468
|
return await handleAsk(args, config);
|
|
551
469
|
case "codiedev_search":
|
|
552
470
|
return await handleSearch(args, config);
|
|
553
471
|
case "codiedev_get_library":
|
|
554
472
|
return await handleGetLibrary(args, config);
|
|
555
|
-
case "codiedev_reverse_ticket":
|
|
556
|
-
return await handleReverseTicket(args, config);
|
|
557
473
|
default:
|
|
558
474
|
return {
|
|
559
475
|
isError: true,
|
|
@@ -577,7 +493,7 @@ async function handlePush(args, config) {
|
|
|
577
493
|
}
|
|
578
494
|
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/push", {
|
|
579
495
|
config,
|
|
580
|
-
body: { filename, markdown, type, kind, tags, force },
|
|
496
|
+
body: { filename, markdown: (0, skillMarker_1.stripSkillMarker)(markdown), type, kind, tags, force },
|
|
581
497
|
});
|
|
582
498
|
const tagSummary = res.tags && res.tags.length > 0 ? `, tags=[${res.tags.join(", ")}]` : "";
|
|
583
499
|
return {
|
|
@@ -609,7 +525,9 @@ async function handlePull(args, config) {
|
|
|
609
525
|
`**${kindLabel}** · v${a.version ?? 1} · ${a.lifecycle ?? "draft"}${tagSuffix} · ` +
|
|
610
526
|
`updated ${(0, shared_1.timeAgo)(a.updatedAt ?? a.createdAt)}\n\n---\n\n`;
|
|
611
527
|
return {
|
|
612
|
-
|
|
528
|
+
// A skill's text carries its identity marker, so a copy the agent writes
|
|
529
|
+
// to disk still attributes its runs exactly (skillMarker.ts).
|
|
530
|
+
content: [{ type: "text", text: header + (a.kind === "skill" || (!a.kind && (0, skillMarker_1.looksLikeSkill)(a.markdown)) ? (0, skillMarker_1.stampSkillMarker)(a.markdown, a._id) : a.markdown) }],
|
|
613
531
|
};
|
|
614
532
|
}
|
|
615
533
|
async function handleDelete(args, config) {
|
|
@@ -705,21 +623,6 @@ async function handleNote(args, config) {
|
|
|
705
623
|
content: [{ type: "text", text: `✓ Noted (${res.key})` }],
|
|
706
624
|
};
|
|
707
625
|
}
|
|
708
|
-
async function handlePromote(args, config) {
|
|
709
|
-
const artifactId = asString(args.artifactId);
|
|
710
|
-
const keyOverride = asStringOrUndefined(args.keyOverride);
|
|
711
|
-
if (!artifactId)
|
|
712
|
-
throw new Error("artifactId required");
|
|
713
|
-
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/promote", { config, body: { artifactId, keyOverride } });
|
|
714
|
-
return {
|
|
715
|
-
content: [
|
|
716
|
-
{
|
|
717
|
-
type: "text",
|
|
718
|
-
text: `✓ Promoted to authored artifact ${res.key} (id=${res.artifactId}).`,
|
|
719
|
-
},
|
|
720
|
-
],
|
|
721
|
-
};
|
|
722
|
-
}
|
|
723
626
|
async function handleShareWith(args, config) {
|
|
724
627
|
const filename = asString(args.filename);
|
|
725
628
|
const to = asString(args.to);
|
|
@@ -810,26 +713,6 @@ async function handleSendTo(args, config) {
|
|
|
810
713
|
return formatAmbiguousRecipient(err, to);
|
|
811
714
|
}
|
|
812
715
|
}
|
|
813
|
-
async function handleReact(args, config) {
|
|
814
|
-
const postId = asString(args.postId);
|
|
815
|
-
const emoji = asString(args.emoji);
|
|
816
|
-
if (!postId)
|
|
817
|
-
throw new Error("postId required");
|
|
818
|
-
if (!emoji)
|
|
819
|
-
throw new Error("emoji required");
|
|
820
|
-
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/react", {
|
|
821
|
-
config,
|
|
822
|
-
body: { postId, emoji },
|
|
823
|
-
});
|
|
824
|
-
const verb = res.action === "added"
|
|
825
|
-
? "Reacted"
|
|
826
|
-
: res.action === "removed"
|
|
827
|
-
? "Removed reaction"
|
|
828
|
-
: "Swapped reaction to";
|
|
829
|
-
return {
|
|
830
|
-
content: [{ type: "text", text: `✓ ${verb} ${res.emoji}` }],
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
716
|
async function handleSearch(args, config) {
|
|
834
717
|
const query = asString(args.query);
|
|
835
718
|
const limit = asIntOrUndefined(args.limit);
|
|
@@ -937,223 +820,6 @@ function formatAmbiguousRecipient(err, to) {
|
|
|
937
820
|
}
|
|
938
821
|
throw err;
|
|
939
822
|
}
|
|
940
|
-
async function handleReverseTicket(args, config) {
|
|
941
|
-
const explicitTitle = asStringOrUndefined(args.title);
|
|
942
|
-
const explicitBase = asStringOrUndefined(args.base);
|
|
943
|
-
const forcedKey = asStringOrUndefined(args.forcedArtifactKey);
|
|
944
|
-
// Helper that runs git from cwd with a hard-fail on error.
|
|
945
|
-
const { execSync } = await Promise.resolve().then(() => __importStar(require("child_process")));
|
|
946
|
-
const git = (cmd) => {
|
|
947
|
-
try {
|
|
948
|
-
return execSync(`git ${cmd}`, {
|
|
949
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
950
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
951
|
-
})
|
|
952
|
-
.toString("utf8")
|
|
953
|
-
.trim();
|
|
954
|
-
}
|
|
955
|
-
catch (err) {
|
|
956
|
-
throw new Error(`git ${cmd} failed: ${err.message}`);
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
// Confirm we're in a git repo.
|
|
960
|
-
try {
|
|
961
|
-
git("rev-parse --is-inside-work-tree");
|
|
962
|
-
}
|
|
963
|
-
catch {
|
|
964
|
-
throw new Error("Not inside a git repository. Run this from a checked-out repo.");
|
|
965
|
-
}
|
|
966
|
-
// Resolve base ref — explicit, then origin/main, then origin/master.
|
|
967
|
-
const candidates = explicitBase
|
|
968
|
-
? [explicitBase]
|
|
969
|
-
: ["origin/main", "origin/master"];
|
|
970
|
-
let baseRef = null;
|
|
971
|
-
for (const c of candidates) {
|
|
972
|
-
try {
|
|
973
|
-
git(`rev-parse --verify ${c}`);
|
|
974
|
-
baseRef = c;
|
|
975
|
-
break;
|
|
976
|
-
}
|
|
977
|
-
catch {
|
|
978
|
-
// try next
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
if (!baseRef) {
|
|
982
|
-
throw new Error(`Couldn't find a base ref (tried ${candidates.join(", ")}). ` +
|
|
983
|
-
`Pass an explicit base via the 'base' argument.`);
|
|
984
|
-
}
|
|
985
|
-
const branch = git("rev-parse --abbrev-ref HEAD");
|
|
986
|
-
if (branch === "HEAD") {
|
|
987
|
-
throw new Error("Detached HEAD — checkout a branch first.");
|
|
988
|
-
}
|
|
989
|
-
const baseSha = git(`merge-base HEAD ${baseRef}`);
|
|
990
|
-
const headSha = git("rev-parse HEAD");
|
|
991
|
-
// Filter generated/lockfile noise so the LLM's diff budget gets spent on
|
|
992
|
-
// real source. The --stat block (collected separately below) still includes
|
|
993
|
-
// every file untouched, so excluded files still appear in the ticket's
|
|
994
|
-
// "Other files touched" section if relevant.
|
|
995
|
-
const isNoise = (p) => /(^|\/)(node_modules|dist|build|\.next|\.turbo|coverage|out)\//.test(p) ||
|
|
996
|
-
/(^|\/)package-lock\.json$/.test(p) ||
|
|
997
|
-
/(^|\/)yarn\.lock$/.test(p) ||
|
|
998
|
-
/(^|\/)pnpm-lock\.yaml$/.test(p) ||
|
|
999
|
-
/(^|\/)bun\.lock(b)?$/.test(p) ||
|
|
1000
|
-
/(^|\/)Cargo\.lock$/.test(p) ||
|
|
1001
|
-
/(^|\/)Gemfile\.lock$/.test(p) ||
|
|
1002
|
-
/(^|\/)poetry\.lock$/.test(p) ||
|
|
1003
|
-
/(^|\/)go\.sum$/.test(p);
|
|
1004
|
-
// Helper: run a shell command tolerating non-zero exit codes (git diff
|
|
1005
|
-
// --no-index exits 1 when files differ, which is the normal case).
|
|
1006
|
-
// Reuses the execSync already imported above for the git() helper.
|
|
1007
|
-
const shellTolerant = (cmd) => {
|
|
1008
|
-
try {
|
|
1009
|
-
return execSync(`${cmd} || true`, {
|
|
1010
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1011
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
1012
|
-
})
|
|
1013
|
-
.toString("utf8")
|
|
1014
|
-
.trim();
|
|
1015
|
-
}
|
|
1016
|
-
catch {
|
|
1017
|
-
return "";
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
const shQuote = (s) => `'${s.replace(/'/g, "'\\''")}'`;
|
|
1021
|
-
// Capture all work the developer has done relative to the base ref:
|
|
1022
|
-
// - committed changes between base and HEAD
|
|
1023
|
-
// - uncommitted modifications to tracked files
|
|
1024
|
-
// - new untracked files (path listed + content via synthetic diff)
|
|
1025
|
-
// Matches the user's intent ("ticket my current changes") rather than the
|
|
1026
|
-
// narrower "only what's been committed" interpretation.
|
|
1027
|
-
const trackedRaw = git(`diff --name-only ${baseSha}`);
|
|
1028
|
-
const trackedFiles = trackedRaw ? trackedRaw.split("\n").filter(Boolean) : [];
|
|
1029
|
-
const untrackedRaw = safeGit(() => git("ls-files --others --exclude-standard"));
|
|
1030
|
-
const untrackedFiles = untrackedRaw
|
|
1031
|
-
? untrackedRaw.split("\n").filter(Boolean)
|
|
1032
|
-
: [];
|
|
1033
|
-
const filesChanged = Array.from(new Set([...trackedFiles, ...untrackedFiles]));
|
|
1034
|
-
if (filesChanged.length === 0) {
|
|
1035
|
-
throw new Error(`No changes detected between ${baseRef} and your working tree on ${branch}.`);
|
|
1036
|
-
}
|
|
1037
|
-
// --stat block: compact ground truth for "every file touched". Tracked
|
|
1038
|
-
// changes from `git diff --stat baseSha`; untracked files appended as
|
|
1039
|
-
// synthesized stat rows so the LLM sees them with line counts.
|
|
1040
|
-
let stat = "";
|
|
1041
|
-
try {
|
|
1042
|
-
const trackedStat = git(`diff --stat=200 ${baseSha}`);
|
|
1043
|
-
const untrackedStatLines = untrackedFiles.map((f) => {
|
|
1044
|
-
const wc = shellTolerant(`wc -l < ${shQuote(f)}`);
|
|
1045
|
-
const n = parseInt(wc.split(/\s+/)[0] || "0", 10) || 0;
|
|
1046
|
-
return ` ${f} | ${n} ++++++++++ (new file)`;
|
|
1047
|
-
});
|
|
1048
|
-
stat = [trackedStat, ...untrackedStatLines]
|
|
1049
|
-
.filter(Boolean)
|
|
1050
|
-
.join("\n")
|
|
1051
|
-
.slice(0, 8_000);
|
|
1052
|
-
}
|
|
1053
|
-
catch {
|
|
1054
|
-
// Best effort.
|
|
1055
|
-
}
|
|
1056
|
-
// Diff body: 40k cap, noise-filtered. Tracked changes via `git diff baseSha`,
|
|
1057
|
-
// plus synthetic add-only diffs for untracked files via `git diff --no-index`.
|
|
1058
|
-
let diff = "";
|
|
1059
|
-
try {
|
|
1060
|
-
const sourceTracked = trackedFiles.filter((f) => !isNoise(f));
|
|
1061
|
-
const trackedDiff = sourceTracked.length > 0
|
|
1062
|
-
? git(`diff ${baseSha} -- ${sourceTracked.map(shQuote).join(" ")}`)
|
|
1063
|
-
: git(`diff ${baseSha}`);
|
|
1064
|
-
const sourceUntracked = untrackedFiles.filter((f) => !isNoise(f));
|
|
1065
|
-
const untrackedDiffs = sourceUntracked.map((f) => shellTolerant(`git diff --no-index --no-color -- /dev/null ${shQuote(f)}`));
|
|
1066
|
-
diff = [trackedDiff, ...untrackedDiffs]
|
|
1067
|
-
.filter(Boolean)
|
|
1068
|
-
.join("\n")
|
|
1069
|
-
.slice(0, 40_000);
|
|
1070
|
-
}
|
|
1071
|
-
catch {
|
|
1072
|
-
// Best effort.
|
|
1073
|
-
}
|
|
1074
|
-
// Repo name from origin remote.
|
|
1075
|
-
let repo;
|
|
1076
|
-
try {
|
|
1077
|
-
const remoteUrl = git("config --get remote.origin.url");
|
|
1078
|
-
const m = remoteUrl.match(/[:/]([^/:]+)\/([^/:]+?)(?:\.git)?$/);
|
|
1079
|
-
if (!m) {
|
|
1080
|
-
throw new Error(`Couldn't parse owner/repo from remote: ${remoteUrl}`);
|
|
1081
|
-
}
|
|
1082
|
-
repo = `${m[1]}/${m[2]}`;
|
|
1083
|
-
}
|
|
1084
|
-
catch (err) {
|
|
1085
|
-
throw new Error(`Couldn't read git remote.origin.url: ${err.message}`);
|
|
1086
|
-
}
|
|
1087
|
-
const title = explicitTitle ?? safeGit(() => git("log -1 --format=%s")) ?? `WIP: ${branch}`;
|
|
1088
|
-
const author = safeGit(() => git("config --get user.name"));
|
|
1089
|
-
const authorEmail = safeGit(() => git("config --get user.email"));
|
|
1090
|
-
// Post to backend.
|
|
1091
|
-
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/reverseTicketFromBranch", {
|
|
1092
|
-
config,
|
|
1093
|
-
body: {
|
|
1094
|
-
branch: {
|
|
1095
|
-
repo,
|
|
1096
|
-
branch,
|
|
1097
|
-
baseSha,
|
|
1098
|
-
headSha,
|
|
1099
|
-
title,
|
|
1100
|
-
author,
|
|
1101
|
-
authorEmail,
|
|
1102
|
-
filesChanged,
|
|
1103
|
-
diff: diff || undefined,
|
|
1104
|
-
stat: stat || undefined,
|
|
1105
|
-
},
|
|
1106
|
-
forcedArtifactKey: forcedKey,
|
|
1107
|
-
},
|
|
1108
|
-
});
|
|
1109
|
-
if (!res.ok || !res.artifactId) {
|
|
1110
|
-
return {
|
|
1111
|
-
isError: true,
|
|
1112
|
-
content: [
|
|
1113
|
-
{
|
|
1114
|
-
type: "text",
|
|
1115
|
-
text: "Reverse-ticket generation returned no result. The writer may have " +
|
|
1116
|
-
"skipped due to missing context (no captured sessions, etc.). Try " +
|
|
1117
|
-
"again after working on the branch a bit more, or check the " +
|
|
1118
|
-
"server logs.",
|
|
1119
|
-
},
|
|
1120
|
-
],
|
|
1121
|
-
};
|
|
1122
|
-
}
|
|
1123
|
-
// Always return an absolute URL so MCP clients (Copilot Chat, Claude Code,
|
|
1124
|
-
// etc.) can render the link correctly without guessing a host. Default to
|
|
1125
|
-
// codiedev.com; allow override via CODIEDEV_PORTAL_URL for self-hosted.
|
|
1126
|
-
const portalHost = (process.env.CODIEDEV_PORTAL_URL || "https://codiedev.com").replace(/\/$/, "");
|
|
1127
|
-
const portalLink = res.portalUrl.startsWith("http")
|
|
1128
|
-
? res.portalUrl
|
|
1129
|
-
: `${portalHost}${res.portalUrl.startsWith("/") ? "" : "/"}${res.portalUrl}`;
|
|
1130
|
-
const verb = res.action === "overwritten" ? "Updated" : "Created";
|
|
1131
|
-
return {
|
|
1132
|
-
content: [
|
|
1133
|
-
{
|
|
1134
|
-
type: "text",
|
|
1135
|
-
text: `✓ ${verb} reverse-ticket draft for branch \`${branch}\` (${repo}).\n` +
|
|
1136
|
-
` files changed: ${filesChanged.length}\n` +
|
|
1137
|
-
` base: ${baseSha.slice(0, 12)} → head: ${headSha.slice(0, 12)} (${baseRef})\n` +
|
|
1138
|
-
` diff size: ${diff.length} chars\n` +
|
|
1139
|
-
` artifact: ${res.artifactKey ?? res.artifactId}\n\n` +
|
|
1140
|
-
`Open in your portal to review, edit, and push to Jira:\n` +
|
|
1141
|
-
` ${portalLink}\n\n` +
|
|
1142
|
-
`Each call creates a fresh draft — if you've made several for the ` +
|
|
1143
|
-
`same branch, clean them up from the portal.`,
|
|
1144
|
-
},
|
|
1145
|
-
],
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
// Run a git invocation; return undefined instead of throwing.
|
|
1149
|
-
function safeGit(fn) {
|
|
1150
|
-
try {
|
|
1151
|
-
return fn();
|
|
1152
|
-
}
|
|
1153
|
-
catch {
|
|
1154
|
-
return undefined;
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
823
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1158
824
|
// Helpers
|
|
1159
825
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Is this a SKILL.md-style file (frontmatter with name + description)? */
|
|
2
|
+
export declare function looksLikeSkill(markdown: string): boolean;
|
|
3
|
+
/** Remove every marker line (idempotent). */
|
|
4
|
+
export declare function stripSkillMarker(markdown: string): string;
|
|
5
|
+
/** Stamp (or re-stamp) the marker. Leaves the text alone for a bad id. */
|
|
6
|
+
export declare function stampSkillMarker(markdown: string, artifactId: string | undefined | null): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Skill identity marker. Two authors can each publish a skill with the same
|
|
3
|
+
// name ("/ticket"); a session's transcript only records the name it ran. A
|
|
4
|
+
// company skill written to disk therefore carries the artifact it came from:
|
|
5
|
+
//
|
|
6
|
+
// <!-- codiedev:artifact=<artifact id> -->
|
|
7
|
+
//
|
|
8
|
+
// appended after the body (frontmatter untouched, invisible when rendered).
|
|
9
|
+
// When the harness loads the skill, the marker lands in the transcript and
|
|
10
|
+
// parseCaptureSignals reports it as signals.skillInvocationArtifacts, so the
|
|
11
|
+
// portal attributes the run to the exact skill. `push` strips it, so a
|
|
12
|
+
// re-pushed copy never claims someone else's artifact id.
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.looksLikeSkill = looksLikeSkill;
|
|
15
|
+
exports.stripSkillMarker = stripSkillMarker;
|
|
16
|
+
exports.stampSkillMarker = stampSkillMarker;
|
|
17
|
+
const ID = /^[a-z0-9]{16,64}$/i;
|
|
18
|
+
const LINE = /^[ \t]*<!--\s*codiedev:artifact=[a-z0-9]+\s*-->[ \t]*(?:\r?\n|$)/gim;
|
|
19
|
+
/** Is this a SKILL.md-style file (frontmatter with name + description)? */
|
|
20
|
+
function looksLikeSkill(markdown) {
|
|
21
|
+
const fm = markdown.match(/^?---\r?\n([\s\S]*?)\r?\n---/);
|
|
22
|
+
if (!fm)
|
|
23
|
+
return false;
|
|
24
|
+
return /^name\s*:/im.test(fm[1]) && /^description\s*:/im.test(fm[1]);
|
|
25
|
+
}
|
|
26
|
+
/** Remove every marker line (idempotent). */
|
|
27
|
+
function stripSkillMarker(markdown) {
|
|
28
|
+
return markdown.replace(LINE, "");
|
|
29
|
+
}
|
|
30
|
+
/** Stamp (or re-stamp) the marker. Leaves the text alone for a bad id. */
|
|
31
|
+
function stampSkillMarker(markdown, artifactId) {
|
|
32
|
+
if (!artifactId || !ID.test(artifactId))
|
|
33
|
+
return markdown;
|
|
34
|
+
const body = stripSkillMarker(markdown);
|
|
35
|
+
const sep = body.endsWith("\n") ? "" : "\n";
|
|
36
|
+
return `${body}${sep}<!-- codiedev:artifact=${artifactId} -->\n`;
|
|
37
|
+
}
|
package/dist/subagents.js
CHANGED
|
@@ -87,10 +87,14 @@ function foldSubagentSignals(parent, subs, source) {
|
|
|
87
87
|
const invocations = new Set(parent.skillInvocations);
|
|
88
88
|
const pulls = new Set(parent.skillPulls);
|
|
89
89
|
const tools = new Map(parent.toolCalls.map((t) => [t.name, t.calls]));
|
|
90
|
+
const artifacts = new Map((parent.skillInvocationArtifacts ?? []).map((a) => [a.skill, a.artifactId]));
|
|
90
91
|
let truncated = parent.truncated;
|
|
91
92
|
for (const content of subs.contents) {
|
|
92
93
|
const s = (0, captureSignals_1.parseCaptureSignals)(content, source);
|
|
93
94
|
add(s.skillInvocationCounts, s.skillInvocations);
|
|
95
|
+
for (const a of s.skillInvocationArtifacts ?? [])
|
|
96
|
+
if (!artifacts.has(a.skill))
|
|
97
|
+
artifacts.set(a.skill, a.artifactId);
|
|
94
98
|
for (const k of s.skillInvocations)
|
|
95
99
|
invocations.add(k);
|
|
96
100
|
for (const k of s.skillPulls)
|
|
@@ -108,6 +112,9 @@ function foldSubagentSignals(parent, subs, source) {
|
|
|
108
112
|
...parent,
|
|
109
113
|
skillInvocations: cap([...invocations].sort()),
|
|
110
114
|
skillInvocationCounts: cap([...counts].map(([skill, count]) => ({ skill, count: Math.min(count, 10_000) })).sort((a, b) => a.skill.localeCompare(b.skill))),
|
|
115
|
+
...(artifacts.size
|
|
116
|
+
? { skillInvocationArtifacts: cap([...artifacts].map(([skill, artifactId]) => ({ skill, artifactId })).sort((a, b) => a.skill.localeCompare(b.skill))) }
|
|
117
|
+
: {}),
|
|
111
118
|
skillPulls: cap([...pulls].sort()),
|
|
112
119
|
toolCalls: cap([...tools].map(([name, calls]) => ({ name, calls })).sort((a, b) => a.name.localeCompare(b.name))),
|
|
113
120
|
subagents: { count: subs.count, truncated: subs.truncated },
|
package/dist/utils.d.ts
CHANGED
|
@@ -147,10 +147,6 @@ export declare function installCursorMcp(): void;
|
|
|
147
147
|
* Claude / Cursor: top-level key is `servers` (not `mcpServers`). On first
|
|
148
148
|
* Copilot Chat run after install, VS Code prompts the user to trust the
|
|
149
149
|
* server — that's expected and documented in the connect success message.
|
|
150
|
-
*
|
|
151
|
-
* Reverse-ticket and the other on-demand verbs work in Copilot identically
|
|
152
|
-
* to Claude Code since the MCP server's reverse_ticket handler is already
|
|
153
|
-
* 100% diff-driven (no transcript dependency).
|
|
154
150
|
*/
|
|
155
151
|
export declare function installVSCodeMcp(): void;
|
|
156
152
|
/**
|
package/dist/utils.js
CHANGED
|
@@ -1502,14 +1502,12 @@ thought, use the \`codiedev\` CLI via Bash:**
|
|
|
1502
1502
|
| "any messages?" / "check my inbox" / "what did a teammate say?" | \`codiedev inbox\` |
|
|
1503
1503
|
| "read that reply" / "mark that ping read" | \`codiedev read <ping-id>\` |
|
|
1504
1504
|
| "note that X is a follow-up" / "remember X" | \`codiedev note "<text>"\` |
|
|
1505
|
-
| "promote the extracted spec" | \`codiedev promote <artifact-id>\` |
|
|
1506
1505
|
| "create a ticket" / "scope this into tickets" / "ticket this for me" | \`codiedev create-ticket [optional-inline-prompt]\` |
|
|
1507
1506
|
| "share with a teammate" / "give someone access" (no notification) | \`codiedev share <key> <name> [--role read\\|edit]\` |
|
|
1508
1507
|
| "send this to a teammate" / "ask someone to look at this" | \`codiedev send <key> <name> ["<msg>"]\` |
|
|
1509
1508
|
| "find something about X" / "has anyone solved Y?" | \`codiedev search "<query>" [--limit N]\` |
|
|
1510
1509
|
| "why did we…?" / "what does the team know about X?" | \`codiedev ask "<question>"\` (synthesized answer + citations) |
|
|
1511
1510
|
| "show my library" / "what artifacts exist?" | \`codiedev library [--scope mine\\|shared\\|all] [--kind doc\\|skill]\` |
|
|
1512
|
-
| "react 🛠 to that post" / "mark as used" | \`codiedev react <postId> <emoji>\` |
|
|
1513
1511
|
|
|
1514
1512
|
**Just push the file — we'll figure out where it goes.** Every artifact
|
|
1515
1513
|
is classified into one of two kinds at upload time:
|
|
@@ -1706,10 +1704,6 @@ function installCursorMcp() {
|
|
|
1706
1704
|
* Claude / Cursor: top-level key is `servers` (not `mcpServers`). On first
|
|
1707
1705
|
* Copilot Chat run after install, VS Code prompts the user to trust the
|
|
1708
1706
|
* server — that's expected and documented in the connect success message.
|
|
1709
|
-
*
|
|
1710
|
-
* Reverse-ticket and the other on-demand verbs work in Copilot identically
|
|
1711
|
-
* to Claude Code since the MCP server's reverse_ticket handler is already
|
|
1712
|
-
* 100% diff-driven (no transcript dependency).
|
|
1713
1707
|
*/
|
|
1714
1708
|
function installVSCodeMcp() {
|
|
1715
1709
|
if (!fs.existsSync(VSCODE_USER_DIR)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.67",
|
|
4
4
|
"description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration — and run the governed Codie harness",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codiedev": "dist/cli.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function runPromote(args: string[]): Promise<void>;
|
package/dist/commands/promote.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runPromote = runPromote;
|
|
4
|
-
const shared_1 = require("./shared");
|
|
5
|
-
function parseArgs(args) {
|
|
6
|
-
let artifactId;
|
|
7
|
-
let keyOverride;
|
|
8
|
-
for (let i = 0; i < args.length; i++) {
|
|
9
|
-
const a = args[i];
|
|
10
|
-
if ((a === "--key" || a === "-k") && i + 1 < args.length) {
|
|
11
|
-
keyOverride = args[++i];
|
|
12
|
-
}
|
|
13
|
-
else if (a.startsWith("--key=")) {
|
|
14
|
-
keyOverride = a.slice("--key=".length);
|
|
15
|
-
}
|
|
16
|
-
else if (!a.startsWith("--") && !artifactId) {
|
|
17
|
-
artifactId = a;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
if (!artifactId) {
|
|
21
|
-
console.error("Usage: codiedev promote <extracted-artifact-id> [--key spec-foo.md]");
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
return { artifactId, keyOverride };
|
|
25
|
-
}
|
|
26
|
-
async function runPromote(args) {
|
|
27
|
-
const { artifactId, keyOverride } = parseArgs(args);
|
|
28
|
-
const config = (0, shared_1.requireConfig)();
|
|
29
|
-
const start = Date.now();
|
|
30
|
-
try {
|
|
31
|
-
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/promote", {
|
|
32
|
-
config,
|
|
33
|
-
body: { artifactId, keyOverride },
|
|
34
|
-
});
|
|
35
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
36
|
-
tool: "codiedev_promote",
|
|
37
|
-
ok: true,
|
|
38
|
-
latencyMs: Date.now() - start,
|
|
39
|
-
});
|
|
40
|
-
console.log(`✓ Promoted to authored artifact ${res.key}`);
|
|
41
|
-
console.log(` id: ${res.artifactId}`);
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
45
|
-
tool: "codiedev_promote",
|
|
46
|
-
ok: false,
|
|
47
|
-
latencyMs: Date.now() - start,
|
|
48
|
-
error: err.message,
|
|
49
|
-
});
|
|
50
|
-
console.error(`Promote failed: ${err.message}`);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
}
|
package/dist/commands/react.d.ts
DELETED
package/dist/commands/react.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
// `codiedev react <postId> <emoji>` — toggle a reaction on a feed post.
|
|
3
|
-
// Calling twice with the same emoji removes it.
|
|
4
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.runReact = runReact;
|
|
6
|
-
const shared_1 = require("./shared");
|
|
7
|
-
async function runReact(args, configOverride) {
|
|
8
|
-
const positionals = args.filter((a) => !a.startsWith("--"));
|
|
9
|
-
const [postId, emoji] = positionals;
|
|
10
|
-
if (!postId || !emoji) {
|
|
11
|
-
console.error("Usage: codiedev react <postId> <emoji>");
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
const config = configOverride ?? (0, shared_1.requireConfig)();
|
|
15
|
-
const start = Date.now();
|
|
16
|
-
try {
|
|
17
|
-
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/react", {
|
|
18
|
-
config,
|
|
19
|
-
body: { postId, emoji },
|
|
20
|
-
});
|
|
21
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
22
|
-
tool: "codiedev_react",
|
|
23
|
-
ok: true,
|
|
24
|
-
latencyMs: Date.now() - start,
|
|
25
|
-
});
|
|
26
|
-
console.log(res.action === "removed"
|
|
27
|
-
? `Removed ${res.emoji ?? emoji} from post ${postId}.`
|
|
28
|
-
: `Reacted to post ${postId} with ${res.emoji ?? emoji}.`);
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
32
|
-
tool: "codiedev_react",
|
|
33
|
-
ok: false,
|
|
34
|
-
latencyMs: Date.now() - start,
|
|
35
|
-
error: err.message,
|
|
36
|
-
});
|
|
37
|
-
console.error(`React failed: ${err.message}`);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
}
|