teamai-cli 0.22.0-beta.2 → 0.22.0-beta.3

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 (2) hide show
  1. package/dist/index.js +275 -168
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -221,7 +221,6 @@ __export(types_exports, {
221
221
  TEAMAI_RECALL_RULES_START: () => TEAMAI_RECALL_RULES_START,
222
222
  TEAMAI_RULES_END: () => TEAMAI_RULES_END,
223
223
  TEAMAI_RULES_START: () => TEAMAI_RULES_START,
224
- TEAMAI_SESSIONS_DIR: () => TEAMAI_SESSIONS_DIR,
225
224
  TEAMAI_SOURCES_DIR: () => TEAMAI_SOURCES_DIR,
226
225
  TEAMAI_STATE_PATH: () => TEAMAI_STATE_PATH,
227
226
  TEAMAI_TOKEN_PATH: () => TEAMAI_TOKEN_PATH,
@@ -398,7 +397,7 @@ function getPushignorePath() {
398
397
  function areTeamHooksDisabled() {
399
398
  return process.env.TEAMAI_HOOKS_DISABLED === "1" || process.env.TEAMAI_HOOKS_DISABLED === "true";
400
399
  }
401
- var ToolPathsSchema, ScopeEnum, SharingConfigSchema, SourceConfigSchema, SOURCE_PULL_TTL_MS, TEAMAI_SOURCES_DIR, TeamaiConfigSchema, MemberConfigSchema, LocalConfigSchema, PendingPushItemSchema, PendingPushSchema, StateSchema, TEAMAI_HOME, TEAMAI_CONFIG_PATH, TEAMAI_STATE_PATH, TEAMAI_TOKEN_PATH, TEAMAI_UPDATE_LOCK_PATH, RESOURCE_TYPES, TEAMAI_RULES_START, TEAMAI_RULES_END, TEAMAI_HOOK_DESCRIPTION_PREFIX, TEAMAI_CUSTOM_HOOK_PREFIX, TEAMAI_AGENT_HOOK_PREFIX, TEAMAI_ENV_START, TEAMAI_ENV_END, TEAMAI_CULTURE_START, TEAMAI_CULTURE_END, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, TEAMAI_RECALL_RULES_START, TEAMAI_RECALL_RULES_END, SKILL_NAME_REGEX, TEAMAI_USAGE_PATH, TEAMAI_KNOWN_SKILLS_PATH, TEAMAI_PUSHIGNORE_PATH, TEAMAI_SESSIONS_DIR, SESSION_LOGS_LOCAL_DIR, UsageEventSchema, DASHBOARD_EVENTS_DIR, DASHBOARD_EVENTS_PATH, DASHBOARD_DEFAULT_PORT, DASHBOARD_IDLE_TIMEOUT_MS, DASHBOARD_STALE_TIMEOUT_MS, DASHBOARD_COMPACTION_THRESHOLD, DASHBOARD_STOPPED_DISPLAY_MS, DASHBOARD_PID_CHECK_INTERVAL_MS, CORRECTION_WINDOW_MS, CORRECTION_KEYWORDS, INTERVENTION_SCAN_MAX_BYTES, TRANSCRIPT_INTERRUPT_PREFIX, TRANSCRIPT_SYSTEM_PREFIXES, TRANSCRIPT_REJECT_MARKERS, CONTRIBUTE_BASE_THRESHOLD, CONTRIBUTE_SMART_THRESHOLD, CONTRIBUTE_INTERRUPT_WEIGHT, CONTRIBUTE_REJECT_WEIGHT, CONTRIBUTE_CORRECTION_WEIGHT, CONTRIBUTE_TOOLERROR_TIERS, CONTRIBUTE_SKILL_BONUS, CONTRIBUTE_DIVERSITY_BONUS_MAX, CONTRIBUTE_FASTPATH_TTL_MS, CONTRIBUTE_KNOWLEDGE_GAP_BONUS, CONTRIBUTE_LOW_QUALITY_BONUS, CONTRIBUTE_LOW_QUALITY_THRESHOLD, CONTRIBUTE_GIT_COMMIT_DOWNWEIGHT, CONTRIBUTE_SESSIONS_DIR, SEARCH_INDEX_VERSION, LEARNINGS_LOCAL_DIR, SEARCH_INDEX_PATH, VOTES_LOCAL_DIR, CultureCompanySchema, CultureTeamSchema, CultureFrontmatterSchema, REPORTS_BRANCH, REPORTS_WORKTREE_DIRNAME, KNOWLEDGE_WORKTREE_DIRNAME, REPORTS_LOCK_FILENAME, BOOTSTRAP_LOCK_FILENAME;
400
+ var ToolPathsSchema, ScopeEnum, SharingConfigSchema, SourceConfigSchema, SOURCE_PULL_TTL_MS, TEAMAI_SOURCES_DIR, TeamaiConfigSchema, MemberConfigSchema, LocalConfigSchema, PendingPushItemSchema, PendingPushSchema, StateSchema, TEAMAI_HOME, TEAMAI_CONFIG_PATH, TEAMAI_STATE_PATH, TEAMAI_TOKEN_PATH, TEAMAI_UPDATE_LOCK_PATH, RESOURCE_TYPES, TEAMAI_RULES_START, TEAMAI_RULES_END, TEAMAI_HOOK_DESCRIPTION_PREFIX, TEAMAI_CUSTOM_HOOK_PREFIX, TEAMAI_AGENT_HOOK_PREFIX, TEAMAI_ENV_START, TEAMAI_ENV_END, TEAMAI_CULTURE_START, TEAMAI_CULTURE_END, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, TEAMAI_RECALL_RULES_START, TEAMAI_RECALL_RULES_END, SKILL_NAME_REGEX, TEAMAI_USAGE_PATH, TEAMAI_KNOWN_SKILLS_PATH, TEAMAI_PUSHIGNORE_PATH, SESSION_LOGS_LOCAL_DIR, UsageEventSchema, DASHBOARD_EVENTS_DIR, DASHBOARD_EVENTS_PATH, DASHBOARD_DEFAULT_PORT, DASHBOARD_IDLE_TIMEOUT_MS, DASHBOARD_STALE_TIMEOUT_MS, DASHBOARD_COMPACTION_THRESHOLD, DASHBOARD_STOPPED_DISPLAY_MS, DASHBOARD_PID_CHECK_INTERVAL_MS, CORRECTION_WINDOW_MS, CORRECTION_KEYWORDS, INTERVENTION_SCAN_MAX_BYTES, TRANSCRIPT_INTERRUPT_PREFIX, TRANSCRIPT_SYSTEM_PREFIXES, TRANSCRIPT_REJECT_MARKERS, CONTRIBUTE_BASE_THRESHOLD, CONTRIBUTE_SMART_THRESHOLD, CONTRIBUTE_INTERRUPT_WEIGHT, CONTRIBUTE_REJECT_WEIGHT, CONTRIBUTE_CORRECTION_WEIGHT, CONTRIBUTE_TOOLERROR_TIERS, CONTRIBUTE_SKILL_BONUS, CONTRIBUTE_DIVERSITY_BONUS_MAX, CONTRIBUTE_FASTPATH_TTL_MS, CONTRIBUTE_KNOWLEDGE_GAP_BONUS, CONTRIBUTE_LOW_QUALITY_BONUS, CONTRIBUTE_LOW_QUALITY_THRESHOLD, CONTRIBUTE_GIT_COMMIT_DOWNWEIGHT, CONTRIBUTE_SESSIONS_DIR, SEARCH_INDEX_VERSION, LEARNINGS_LOCAL_DIR, SEARCH_INDEX_PATH, VOTES_LOCAL_DIR, CultureCompanySchema, CultureTeamSchema, CultureFrontmatterSchema, REPORTS_BRANCH, REPORTS_WORKTREE_DIRNAME, KNOWLEDGE_WORKTREE_DIRNAME, REPORTS_LOCK_FILENAME, BOOTSTRAP_LOCK_FILENAME;
402
401
  var init_types = __esm({
403
402
  "src/types.ts"() {
404
403
  "use strict";
@@ -678,7 +677,6 @@ var init_types = __esm({
678
677
  TEAMAI_USAGE_PATH = `${TEAMAI_HOME}/usage.jsonl`;
679
678
  TEAMAI_KNOWN_SKILLS_PATH = `${TEAMAI_HOME}/known-skills.json`;
680
679
  TEAMAI_PUSHIGNORE_PATH = `${TEAMAI_HOME}/pushignore`;
681
- TEAMAI_SESSIONS_DIR = `${TEAMAI_HOME}/sessions`;
682
680
  SESSION_LOGS_LOCAL_DIR = `${TEAMAI_HOME}/session-logs`;
683
681
  UsageEventSchema = z.object({
684
682
  skill: z.string().regex(SKILL_NAME_REGEX),
@@ -4842,6 +4840,59 @@ var init_path_safety = __esm({
4842
4840
  }
4843
4841
  });
4844
4842
 
4843
+ // src/utils/frontmatter.ts
4844
+ import matter2 from "gray-matter";
4845
+ function stripBom(text) {
4846
+ return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
4847
+ }
4848
+ function isPlainRecord(value) {
4849
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
4850
+ const prototype = Object.getPrototypeOf(value);
4851
+ return prototype === Object.prototype || prototype === null;
4852
+ }
4853
+ function splitFrontmatter2(content) {
4854
+ const match = content.match(FRONTMATTER_BLOCK);
4855
+ if (!match) {
4856
+ return { data: {}, valid: false, body: stripBom(content), raw: "" };
4857
+ }
4858
+ const rawFull = match[0];
4859
+ const body = content.slice(rawFull.length);
4860
+ const raw = stripBom(rawFull);
4861
+ let data = {};
4862
+ let valid = false;
4863
+ try {
4864
+ const parsed = matter2(raw, {});
4865
+ if (!isPlainRecord(parsed.data)) {
4866
+ return { data: {}, valid: false, body, raw };
4867
+ }
4868
+ data = { ...parsed.data };
4869
+ valid = true;
4870
+ } catch {
4871
+ data = {};
4872
+ }
4873
+ return { data, valid, body, raw };
4874
+ }
4875
+ function parseFrontmatter(content) {
4876
+ const { data, body } = splitFrontmatter2(content);
4877
+ return { data, body };
4878
+ }
4879
+ function stripFrontmatter(content) {
4880
+ return splitFrontmatter2(content).body;
4881
+ }
4882
+ function hasFrontmatter(content) {
4883
+ return FRONTMATTER_BLOCK.test(content);
4884
+ }
4885
+ function stringifyFrontmatter(data, body) {
4886
+ return matter2.stringify(body, data);
4887
+ }
4888
+ var FRONTMATTER_BLOCK;
4889
+ var init_frontmatter = __esm({
4890
+ "src/utils/frontmatter.ts"() {
4891
+ "use strict";
4892
+ FRONTMATTER_BLOCK = /^?---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)/;
4893
+ }
4894
+ });
4895
+
4845
4896
  // src/builtin-hooks.ts
4846
4897
  import fs7 from "fs";
4847
4898
  import path14 from "path";
@@ -8088,7 +8139,6 @@ import path25 from "path";
8088
8139
  import { execFile as execFile2 } from "child_process";
8089
8140
  import { promisify } from "util";
8090
8141
  import fse4 from "fs-extra";
8091
- import YAML5 from "yaml";
8092
8142
  function isUnimplementedCommand(command) {
8093
8143
  const type = command.type ?? "";
8094
8144
  if (IMPLEMENTED_HOOK_COMMAND_TYPES.has(type)) return false;
@@ -9054,14 +9104,7 @@ async function findMarkdownFile(extractDir, preferredName) {
9054
9104
  async function readFrontmatter(filePath) {
9055
9105
  const content = await readFileSafe(filePath);
9056
9106
  if (!content) return {};
9057
- const match = content.match(/^---\n([\s\S]*?)\n---/);
9058
- if (!match) return {};
9059
- try {
9060
- const parsed = YAML5.parse(match[1]);
9061
- return parsed && typeof parsed === "object" ? parsed : {};
9062
- } catch {
9063
- return {};
9064
- }
9107
+ return parseFrontmatter(content).data;
9065
9108
  }
9066
9109
  async function resolveSkillDirName(skillRoot, slug) {
9067
9110
  const fm = await readFrontmatter(path25.join(skillRoot, "SKILL.md"));
@@ -9879,6 +9922,7 @@ var init_local_agent = __esm({
9879
9922
  "src/local-agent.ts"() {
9880
9923
  "use strict";
9881
9924
  init_logger();
9925
+ init_frontmatter();
9882
9926
  init_fs();
9883
9927
  init_base();
9884
9928
  init_resources();
@@ -9938,7 +9982,7 @@ __export(source_exports, {
9938
9982
  });
9939
9983
  import path26 from "path";
9940
9984
  import fse5 from "fs-extra";
9941
- import YAML6 from "yaml";
9985
+ import YAML5 from "yaml";
9942
9986
  function getSourceDir(sourceName) {
9943
9987
  assertSafeResourceName(sourceName);
9944
9988
  return path26.join(getUserHome(), ".teamai", "sources", sourceName);
@@ -10034,12 +10078,12 @@ async function sourceAdd(repoUrl, options) {
10034
10078
  log.error("Could not read teamai.yaml");
10035
10079
  return;
10036
10080
  }
10037
- const raw = YAML6.parse(content);
10081
+ const raw = YAML5.parse(content);
10038
10082
  if (!raw.sources) {
10039
10083
  raw.sources = [];
10040
10084
  }
10041
10085
  raw.sources.push({ name, repo: repoUrl });
10042
- await fse5.writeFile(yamlPath, YAML6.stringify(raw));
10086
+ await fse5.writeFile(yamlPath, YAML5.stringify(raw));
10043
10087
  log.success(`Added source "${name}" (${repoUrl})`);
10044
10088
  log.info("Run `teamai push` to share this change with your team.");
10045
10089
  }
@@ -10061,9 +10105,9 @@ async function sourceRemove(name, options) {
10061
10105
  log.error("Could not read teamai.yaml");
10062
10106
  return;
10063
10107
  }
10064
- const raw = YAML6.parse(content);
10108
+ const raw = YAML5.parse(content);
10065
10109
  raw.sources = (raw.sources ?? []).filter((s) => s.name !== name);
10066
- await fse5.writeFile(yamlPath, YAML6.stringify(raw));
10110
+ await fse5.writeFile(yamlPath, YAML5.stringify(raw));
10067
10111
  await cleanupSourceSkills(name, teamConfig, localConfig);
10068
10112
  const sourceDir = getSourceDir(name);
10069
10113
  if (await pathExists(sourceDir)) {
@@ -10301,16 +10345,10 @@ async function findSkillInRepo(skillsDir, skillName) {
10301
10345
  async function extractSkillDescription(skillDir) {
10302
10346
  const content = await readFileSafe(path26.join(skillDir, "SKILL.md"));
10303
10347
  if (!content) return "";
10304
- const match = content.match(/^---\n([\s\S]*?)\n---/);
10305
- if (!match) return "";
10306
- const frontmatter = match[1];
10307
- const singleMatch = frontmatter.match(/description:\s*["']?(.+?)["']?\s*$/m);
10308
- if (singleMatch) return singleMatch[1].trim();
10309
- const multiMatch = frontmatter.match(/description:\s*>-?\s*\n([\s\S]*?)(?=\n\w|\n---)/);
10310
- if (multiMatch) {
10311
- return multiMatch[1].split("\n").map((l) => l.trim()).filter((l) => l).join(" ");
10312
- }
10313
- return "";
10348
+ const { data } = parseFrontmatter(content);
10349
+ const desc = data["description"];
10350
+ if (!desc) return "";
10351
+ return String(desc).replace(/\s+/g, " ").trim();
10314
10352
  }
10315
10353
  async function getLocalTeamSkillNames(teamConfig, localConfig) {
10316
10354
  const handler = getHandler("skills");
@@ -10358,6 +10396,7 @@ var init_source = __esm({
10358
10396
  "use strict";
10359
10397
  init_config();
10360
10398
  init_git2();
10399
+ init_frontmatter();
10361
10400
  init_providers();
10362
10401
  init_logger();
10363
10402
  init_fs();
@@ -10372,41 +10411,38 @@ var init_source = __esm({
10372
10411
 
10373
10412
  // src/resources/skills.ts
10374
10413
  import path27 from "path";
10414
+ import YAML6 from "yaml";
10415
+ function appendFrontmatterFields(raw, fields) {
10416
+ const eol = raw.includes("\r\n") ? "\r\n" : "\n";
10417
+ const yaml = YAML6.stringify(fields).trimEnd().replace(/\n/g, eol);
10418
+ return raw.replace(
10419
+ /(\r?\n---[ \t]*)(\r?\n|$)$/,
10420
+ (_match, closing, trailing) => `${eol}${yaml}${closing}${trailing}`
10421
+ );
10422
+ }
10375
10423
  async function ensureSkillFrontmatter(skillDir, skillName) {
10376
10424
  const skillMdPath = path27.join(skillDir, SKILL_MD);
10377
10425
  const content = await readFileSafe(skillMdPath);
10378
10426
  if (!content) return false;
10379
- const fmMatch = content.match(FRONTMATTER_REGEX);
10380
- if (!fmMatch) {
10381
- const description = extractDescriptionFromContent(content, skillName);
10382
- const frontmatter = `---
10383
- name: ${skillName}
10384
- description: ${description}
10385
- ---
10386
- `;
10387
- const newContent2 = frontmatter + (content.startsWith("\n") ? content : "\n" + content);
10427
+ const { data, body, raw, valid } = splitFrontmatter2(content);
10428
+ if (!raw) {
10429
+ const description = extractDescriptionFromContent(body, skillName);
10430
+ const newContent2 = stringifyFrontmatter({ name: skillName, description }, body);
10388
10431
  await writeFile(skillMdPath, newContent2);
10389
10432
  log.debug(`Injected YAML frontmatter into ${skillName}/SKILL.md`);
10390
10433
  return true;
10391
10434
  }
10392
- const fmBlock = fmMatch[0];
10393
- const fmBody = fmBlock.slice(4, fmBlock.length - 4);
10394
- const hasName = /^name:\s*.+/m.test(fmBody);
10395
- const hasDescription = /^description:\s*.+/m.test(fmBody);
10435
+ if (!valid) {
10436
+ log.warn(`Could not repair malformed frontmatter in ${skillName}/SKILL.md; leaving it unchanged`);
10437
+ return false;
10438
+ }
10439
+ const hasName = typeof data["name"] === "string" && String(data["name"]).trim() !== "";
10440
+ const hasDescription = typeof data["description"] === "string" && String(data["description"]).trim() !== "";
10396
10441
  if (hasName && hasDescription) return false;
10397
- const lines = fmBody.split("\n");
10398
- if (!hasName) {
10399
- lines.push(`name: ${skillName}`);
10400
- }
10401
- if (!hasDescription) {
10402
- const restContent = content.slice(fmMatch[0].length);
10403
- const description = extractDescriptionFromContent(restContent, skillName);
10404
- lines.push(`description: ${description}`);
10405
- }
10406
- const newFrontmatter = `---
10407
- ${lines.join("\n")}
10408
- ---`;
10409
- const newContent = content.replace(FRONTMATTER_REGEX, newFrontmatter);
10442
+ const missingFields = {};
10443
+ if (!hasName) missingFields.name = skillName;
10444
+ if (!hasDescription) missingFields.description = extractDescriptionFromContent(body, skillName);
10445
+ const newContent = appendFrontmatterFields(raw, missingFields) + body;
10410
10446
  await writeFile(skillMdPath, newContent);
10411
10447
  log.debug(`Added missing frontmatter fields to ${skillName}/SKILL.md`);
10412
10448
  return true;
@@ -10486,7 +10522,7 @@ async function scanSkillsRecursively(dirPath) {
10486
10522
  await walk2(dirPath);
10487
10523
  return results;
10488
10524
  }
10489
- var CONTRIBUTORS_FILE, SKILL_MD, FRONTMATTER_REGEX, SkillsHandler;
10525
+ var CONTRIBUTORS_FILE, SKILL_MD, SkillsHandler;
10490
10526
  var init_skills = __esm({
10491
10527
  "src/resources/skills.ts"() {
10492
10528
  "use strict";
@@ -10499,9 +10535,9 @@ var init_skills = __esm({
10499
10535
  init_hermes_home();
10500
10536
  init_roles();
10501
10537
  init_path_safety();
10538
+ init_frontmatter();
10502
10539
  CONTRIBUTORS_FILE = "CONTRIBUTORS";
10503
10540
  SKILL_MD = "SKILL.md";
10504
- FRONTMATTER_REGEX = /^---\n[\s\S]*?\n---/;
10505
10541
  SkillsHandler = class extends ResourceHandler {
10506
10542
  type = "skills";
10507
10543
  /**
@@ -11404,7 +11440,7 @@ var init_env = __esm({
11404
11440
  // src/resources/agent-format.ts
11405
11441
  import path32 from "path";
11406
11442
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
11407
- import matter2 from "gray-matter";
11443
+ import matter3 from "gray-matter";
11408
11444
  import { stringify as stringifyToml, parse as parseToml } from "smol-toml";
11409
11445
  function agentFileExtensionForTool(tool) {
11410
11446
  switch (tool) {
@@ -11492,7 +11528,7 @@ function renderForCursor(spec) {
11492
11528
  frontmatterData[key] = value;
11493
11529
  }
11494
11530
  }
11495
- const content = matter2.stringify(spec.instructions, frontmatterData);
11531
+ const content = matter3.stringify(spec.instructions, frontmatterData);
11496
11532
  return { ext: agentFileExtensionForTool("cursor"), content };
11497
11533
  }
11498
11534
  function renderForOpencode(spec) {
@@ -11509,7 +11545,7 @@ function renderForOpencode(spec) {
11509
11545
  frontmatterData[key] = value;
11510
11546
  }
11511
11547
  }
11512
- const content = matter2.stringify(spec.instructions, frontmatterData);
11548
+ const content = matter3.stringify(spec.instructions, frontmatterData);
11513
11549
  return { ext: agentFileExtensionForTool("opencode"), content };
11514
11550
  }
11515
11551
  function renderMarkdownAgent(spec, extras) {
@@ -11528,7 +11564,7 @@ function renderMarkdownAgent(spec, extras) {
11528
11564
  frontmatterData[key] = value;
11529
11565
  }
11530
11566
  }
11531
- return matter2.stringify(spec.instructions, frontmatterData);
11567
+ return matter3.stringify(spec.instructions, frontmatterData);
11532
11568
  }
11533
11569
  function renderTomlAgent(spec, extras) {
11534
11570
  const tomlData = {
@@ -11549,7 +11585,7 @@ function renderTomlAgent(spec, extras) {
11549
11585
  function reverseFromClaude(filePath, content) {
11550
11586
  let parsed;
11551
11587
  try {
11552
- parsed = matter2(content);
11588
+ parsed = matter3(content);
11553
11589
  } catch (err) {
11554
11590
  return { ok: false, reason: `parse error: ${err.message}` };
11555
11591
  }
@@ -11613,7 +11649,7 @@ function reverseFromCodex(filePath, content) {
11613
11649
  function reverseFromCursor(filePath, content) {
11614
11650
  let parsed;
11615
11651
  try {
11616
- parsed = matter2(content);
11652
+ parsed = matter3(content);
11617
11653
  } catch (err) {
11618
11654
  return { ok: false, reason: `parse error: ${err.message}` };
11619
11655
  }
@@ -11642,7 +11678,7 @@ function reverseFromCursor(filePath, content) {
11642
11678
  function reverseFromOpencode(filePath, content) {
11643
11679
  let parsed;
11644
11680
  try {
11645
- parsed = matter2(content);
11681
+ parsed = matter3(content);
11646
11682
  } catch (err) {
11647
11683
  return { ok: false, reason: `parse error: ${err.message}` };
11648
11684
  }
@@ -12261,18 +12297,10 @@ async function extractSkillDescription2(skillDir) {
12261
12297
  const skillMdPath = path35.join(skillDir, SKILL_MD2);
12262
12298
  const content = await readFileSafe(skillMdPath);
12263
12299
  if (!content) return "";
12264
- const match = content.match(/^---\n([\s\S]*?)\n---/);
12265
- if (!match) return "";
12266
- const frontmatter = match[1];
12267
- const descMatch = frontmatter.match(/description:\s*>-?\s*\n([\s\S]*?)(?=\n\w|\n---)/);
12268
- if (descMatch) {
12269
- return descMatch[1].split("\n").map((l) => l.trim()).filter((l) => l).join(" ");
12270
- }
12271
- const singleMatch = frontmatter.match(/description:\s*["']?(.+?)["']?\s*$/m);
12272
- if (singleMatch) {
12273
- return singleMatch[1].trim();
12274
- }
12275
- return "";
12300
+ const { data } = parseFrontmatter(content);
12301
+ const desc = data["description"];
12302
+ if (!desc) return "";
12303
+ return String(desc).replace(/\s+/g, " ").trim();
12276
12304
  }
12277
12305
  async function refreshMarketplace(repoPath) {
12278
12306
  const marketplacePath = path35.join(repoPath, MARKETPLACE_PATH);
@@ -12351,6 +12379,7 @@ var init_marketplace = __esm({
12351
12379
  "use strict";
12352
12380
  init_fs();
12353
12381
  init_logger();
12382
+ init_frontmatter();
12354
12383
  MARKETPLACE_PATH = ".codebuddy-plugin/marketplace.json";
12355
12384
  SKILL_MD2 = "SKILL.md";
12356
12385
  }
@@ -15762,7 +15791,7 @@ __export(confidence_exports, {
15762
15791
  writeBackConfidence: () => writeBackConfidence
15763
15792
  });
15764
15793
  import path46 from "path";
15765
- import matter3 from "gray-matter";
15794
+ import matter4 from "gray-matter";
15766
15795
  function computeConfidence(factors) {
15767
15796
  const { recalledCount, upvotedCount, lastRecalledAt } = factors;
15768
15797
  const base = Math.min(1, recalledCount * 0.1 + upvotedCount * 0.3);
@@ -15824,11 +15853,11 @@ async function writeBackConfidence(learningsDir, confidenceMap) {
15824
15853
  const content = await readFileSafe(absPath);
15825
15854
  if (!content) continue;
15826
15855
  try {
15827
- const { data, content: body } = matter3(content);
15856
+ const { data, content: body } = matter4(content);
15828
15857
  const existingConf = typeof data.confidence === "number" ? data.confidence : void 0;
15829
15858
  if (existingConf !== void 0 && Math.abs(existingConf - newConf) <= 0.05) continue;
15830
15859
  data.confidence = newConf;
15831
- const newContent = matter3.stringify(body, data);
15860
+ const newContent = matter4.stringify(body, data);
15832
15861
  await writeFile(absPath, newContent);
15833
15862
  updated++;
15834
15863
  } catch {
@@ -15891,7 +15920,7 @@ __export(search_index_exports, {
15891
15920
  wordSegments: () => wordSegments
15892
15921
  });
15893
15922
  import path47 from "path";
15894
- import matter4 from "gray-matter";
15923
+ import matter5 from "gray-matter";
15895
15924
  function getSearchIndexPath() {
15896
15925
  return path47.join(getUserHome(), ".teamai", "search-index.json");
15897
15926
  }
@@ -15945,7 +15974,7 @@ function inferDomain(frontmatterDomain, tags, filePath, type) {
15945
15974
  function parseLearningDoc(content, filename) {
15946
15975
  if (!content.trim()) return null;
15947
15976
  try {
15948
- const { data, content: body } = matter4(content);
15977
+ const { data, content: body } = matter5(content);
15949
15978
  const meta = {
15950
15979
  title: typeof data.title === "string" ? data.title : void 0,
15951
15980
  author: typeof data.author === "string" ? data.author : void 0,
@@ -15974,8 +16003,8 @@ async function aggregateVotes(votesDir) {
15974
16003
  const content = await readFileSafe(path47.join(votesDir, file));
15975
16004
  if (!content) continue;
15976
16005
  try {
15977
- const YAML20 = (await import("yaml")).default;
15978
- const parsed = YAML20.parse(content);
16006
+ const YAML19 = (await import("yaml")).default;
16007
+ const parsed = YAML19.parse(content);
15979
16008
  if (!parsed?.votes) continue;
15980
16009
  if (parsed.version === 2) {
15981
16010
  const votes = parsed.votes;
@@ -16039,7 +16068,7 @@ async function entryFromMdFile(absPath, filenameForId, type, voteCounts) {
16039
16068
  const tags = meta.tags ?? [];
16040
16069
  const rawFrontmatterDomain = (() => {
16041
16070
  try {
16042
- return matter4(content).data["domain"];
16071
+ return matter5(content).data["domain"];
16043
16072
  } catch {
16044
16073
  return void 0;
16045
16074
  }
@@ -18164,7 +18193,7 @@ __export(pull_exports, {
18164
18193
  });
18165
18194
  import path54 from "path";
18166
18195
  import fse10 from "fs-extra";
18167
- import matter5 from "gray-matter";
18196
+ import matter6 from "gray-matter";
18168
18197
  async function refreshTeamRepo(localConfig) {
18169
18198
  if (localConfig.repo.kind === "http") {
18170
18199
  const { resolveApiKey: resolveApiKey2 } = await Promise.resolve().then(() => (init_api_key(), api_key_exports));
@@ -18783,7 +18812,7 @@ async function pullForScope(localConfig, options, policy = {}) {
18783
18812
  }
18784
18813
  if (!options.silent && !options.dryRun) {
18785
18814
  try {
18786
- const YAML20 = (await import("yaml")).default;
18815
+ const YAML19 = (await import("yaml")).default;
18787
18816
  const { listFiles: listFiles2, readFileSafe: readFileSafe5 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
18788
18817
  const { getRecommendations: getRecommendations2, displayRecommendations: displayRecommendations2 } = await Promise.resolve().then(() => (init_skill_recommend(), skill_recommend_exports));
18789
18818
  let statsDir = path54.join(localConfig.repo.localPath, "stats");
@@ -18802,7 +18831,7 @@ async function pullForScope(localConfig, options, policy = {}) {
18802
18831
  const content = await readFileSafe5(path54.join(statsDir, file));
18803
18832
  if (!content) continue;
18804
18833
  try {
18805
- const parsed = YAML20.parse(content);
18834
+ const parsed = YAML19.parse(content);
18806
18835
  if (parsed?.username && parsed?.skills) teamStats.push(parsed);
18807
18836
  } catch {
18808
18837
  }
@@ -18818,7 +18847,7 @@ async function pullForScope(localConfig, options, policy = {}) {
18818
18847
  function compileCulture(raw) {
18819
18848
  let parsed;
18820
18849
  try {
18821
- parsed = matter5(raw);
18850
+ parsed = matter6(raw);
18822
18851
  } catch {
18823
18852
  return null;
18824
18853
  }
@@ -19212,7 +19241,6 @@ var init_pull = __esm({
19212
19241
 
19213
19242
  // src/agent-skills.ts
19214
19243
  import path55 from "path";
19215
- import YAML16 from "yaml";
19216
19244
  async function buildClassifyContext(localConfig) {
19217
19245
  const teamSkills = await collectTeamRepoSkills(localConfig.repo.localPath);
19218
19246
  const sourceSkills = /* @__PURE__ */ new Map();
@@ -19308,16 +19336,8 @@ async function scanAgentSkills(agent, ctx) {
19308
19336
  async function readSkillDescription(skillMdPath) {
19309
19337
  const content = await readFileSafe(skillMdPath);
19310
19338
  if (!content) return "";
19311
- const fm = content.match(FRONTMATTER_REGEX2);
19312
- if (!fm) return "";
19313
- let parsed;
19314
- try {
19315
- parsed = YAML16.parse(fm[1]);
19316
- } catch {
19317
- return "";
19318
- }
19319
- if (!parsed || typeof parsed !== "object") return "";
19320
- const desc = parsed.description;
19339
+ const { data } = parseFrontmatter(content);
19340
+ const desc = data["description"];
19321
19341
  if (typeof desc !== "string") return "";
19322
19342
  return desc.split("\n").map((l) => l.trim()).filter(Boolean).join(" ");
19323
19343
  }
@@ -19325,7 +19345,6 @@ function truncate(text, max) {
19325
19345
  if (text.length <= max) return text;
19326
19346
  return text.slice(0, Math.max(0, max - 3)) + "...";
19327
19347
  }
19328
- var FRONTMATTER_REGEX2;
19329
19348
  var init_agent_skills = __esm({
19330
19349
  "src/agent-skills.ts"() {
19331
19350
  "use strict";
@@ -19333,7 +19352,7 @@ var init_agent_skills = __esm({
19333
19352
  init_known_agents();
19334
19353
  init_builtin_skills();
19335
19354
  init_home();
19336
- FRONTMATTER_REGEX2 = /^---\n([\s\S]*?)\n---/;
19355
+ init_frontmatter();
19337
19356
  }
19338
19357
  });
19339
19358
 
@@ -19344,7 +19363,7 @@ __export(status_exports, {
19344
19363
  status: () => status
19345
19364
  });
19346
19365
  import path56 from "path";
19347
- import YAML17 from "yaml";
19366
+ import YAML16 from "yaml";
19348
19367
  async function status(options) {
19349
19368
  const { localConfig, teamConfig } = await autoDetectInit();
19350
19369
  const scopeLabel = localConfig.scope;
@@ -19389,7 +19408,7 @@ async function status(options) {
19389
19408
  const envContent = await readFileSafe(envYamlPath);
19390
19409
  if (envContent) {
19391
19410
  try {
19392
- const envData = YAML17.parse(envContent);
19411
+ const envData = YAML16.parse(envContent);
19393
19412
  envCount = Array.isArray(envData?.variables) ? envData.variables.length : 0;
19394
19413
  } catch {
19395
19414
  }
@@ -19476,7 +19495,7 @@ async function printRepoSection(t, options, ctx) {
19476
19495
  const envContent = await readFileSafe(envYamlPath);
19477
19496
  if (envContent) {
19478
19497
  try {
19479
- const envData = YAML17.parse(envContent);
19498
+ const envData = YAML16.parse(envContent);
19480
19499
  if (envData?.variables && envData.variables.length > 0) {
19481
19500
  if (options.reveal) {
19482
19501
  process.stderr.write("[warn] Env values will be shown in plaintext\n");
@@ -19821,14 +19840,14 @@ __export(members_exports, {
19821
19840
  getMemberConfig: () => getMemberConfig,
19822
19841
  listMembers: () => listMembers
19823
19842
  });
19824
- import YAML18 from "yaml";
19843
+ import YAML17 from "yaml";
19825
19844
  import path58 from "path";
19826
19845
  async function getMemberConfig(repoPath, username) {
19827
19846
  const memberPath = path58.join(repoPath, "members", `${username}.yaml`);
19828
19847
  const content = await readFileSafe(memberPath);
19829
19848
  if (!content) return null;
19830
19849
  try {
19831
- const raw = YAML18.parse(content);
19850
+ const raw = YAML17.parse(content);
19832
19851
  return MemberConfigSchema.parse(raw);
19833
19852
  } catch {
19834
19853
  return null;
@@ -19860,7 +19879,7 @@ async function listMembers(options) {
19860
19879
  const content = await readFileSafe(path58.join(membersDir, file));
19861
19880
  if (!content) continue;
19862
19881
  try {
19863
- const raw = YAML18.parse(content);
19882
+ const raw = YAML17.parse(content);
19864
19883
  const member = MemberConfigSchema.parse(raw);
19865
19884
  const isSelf = member.username === localConfig.username;
19866
19885
  const marker = isSelf ? " (you)" : "";
@@ -20232,7 +20251,7 @@ __export(roles_cmd_exports, {
20232
20251
  rolesUpdate: () => rolesUpdate
20233
20252
  });
20234
20253
  import path60 from "path";
20235
- import YAML19 from "yaml";
20254
+ import YAML18 from "yaml";
20236
20255
  function parseNamespaces(input) {
20237
20256
  return input.split(",").map((s) => s.trim()).filter(Boolean);
20238
20257
  }
@@ -20349,7 +20368,7 @@ async function rolesInit(options) {
20349
20368
  };
20350
20369
  if (options.dryRun) {
20351
20370
  log.info("[dry-run] Would write manifest:");
20352
- console.log(YAML19.stringify(manifest));
20371
+ console.log(YAML18.stringify(manifest));
20353
20372
  return;
20354
20373
  }
20355
20374
  const allNamespaces = [...new Set(roles.flatMap((r) => r.resources.skills))];
@@ -22936,6 +22955,42 @@ var init_dashboard = __esm({
22936
22955
  });
22937
22956
 
22938
22957
  // src/hook-dispatch.ts
22958
+ function asObject(value) {
22959
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
22960
+ }
22961
+ function mergeHookOutputs(outputs) {
22962
+ if (outputs.length === 0) return null;
22963
+ if (outputs.length === 1) return outputs[0];
22964
+ let parsed;
22965
+ try {
22966
+ parsed = outputs.map((output) => {
22967
+ const value = asObject(JSON.parse(output));
22968
+ if (!value) throw new Error("Hook output is not an object");
22969
+ return value;
22970
+ });
22971
+ } catch {
22972
+ return outputs[0];
22973
+ }
22974
+ const followups = parsed.map((value) => value.followup_message);
22975
+ if (followups.every((value) => typeof value === "string")) {
22976
+ return JSON.stringify({ ...parsed[0], followup_message: followups.join("\n") });
22977
+ }
22978
+ const specificOutputs = parsed.map((value) => asObject(value.hookSpecificOutput));
22979
+ if (specificOutputs.every((value) => value !== null)) {
22980
+ const contexts = specificOutputs.map((value) => value.additionalContext);
22981
+ const eventNames = new Set(specificOutputs.map((value) => value.hookEventName).filter(Boolean));
22982
+ if (contexts.every((value) => typeof value === "string") && eventNames.size <= 1) {
22983
+ return JSON.stringify({
22984
+ ...parsed[0],
22985
+ hookSpecificOutput: {
22986
+ ...specificOutputs[0],
22987
+ additionalContext: contexts.join("\n")
22988
+ }
22989
+ });
22990
+ }
22991
+ }
22992
+ return outputs[0];
22993
+ }
22939
22994
  function withTimeout2(promise, ms, handlerName) {
22940
22995
  return new Promise((resolve, reject) => {
22941
22996
  const timer = setTimeout(() => {
@@ -22971,7 +23026,7 @@ function createDispatcher(config) {
22971
23026
  return withTimeout2(reg.handler.execute(stdin, tool), timeoutMs, reg.handler.name);
22972
23027
  })
22973
23028
  );
22974
- let output = null;
23029
+ const outputs = [];
22975
23030
  const errors = [];
22976
23031
  for (let i = 0; i < settled.length; i++) {
22977
23032
  const result = settled[i];
@@ -22982,12 +23037,10 @@ function createDispatcher(config) {
22982
23037
  error: result.reason instanceof Error ? result.reason : new Error(String(result.reason))
22983
23038
  });
22984
23039
  } else if (result.status === "fulfilled" && result.value != null) {
22985
- if (output === null) {
22986
- output = result.value;
22987
- }
23040
+ outputs.push(result.value);
22988
23041
  }
22989
23042
  }
22990
- return { output, errors };
23043
+ return { output: mergeHookOutputs(outputs), errors };
22991
23044
  }
22992
23045
  };
22993
23046
  }
@@ -23166,6 +23219,7 @@ var init_hook_output = __esm({
23166
23219
  var contribute_check_exports = {};
23167
23220
  __export(contribute_check_exports, {
23168
23221
  applyPhase2Adjustments: () => applyPhase2Adjustments,
23222
+ claimVotesNudge: () => claimVotesNudge,
23169
23223
  cleanupStaleSessions: () => cleanupStaleSessions,
23170
23224
  computeSmartScore: () => computeSmartScore,
23171
23225
  contributeCheck: () => contributeCheck,
@@ -23173,7 +23227,9 @@ __export(contribute_check_exports, {
23173
23227
  hasGitCommitInSession: () => hasGitCommitInSession,
23174
23228
  markContributed: () => markContributed,
23175
23229
  readContributeState: () => readContributeState,
23230
+ stashVotesHint: () => stashVotesHint,
23176
23231
  takePendingHint: () => takePendingHint,
23232
+ takePendingVotesHint: () => takePendingVotesHint,
23177
23233
  writeContributeState: () => writeContributeState
23178
23234
  });
23179
23235
  import fs22 from "fs";
@@ -23210,6 +23266,22 @@ function getSessionPath(sessionId) {
23210
23266
  `${sanitizeSessionId2(sessionId)}.json`
23211
23267
  );
23212
23268
  }
23269
+ function getPendingVotesHintPath(sessionId) {
23270
+ return path71.join(
23271
+ getUserHome(),
23272
+ ".teamai",
23273
+ "sessions",
23274
+ `${sanitizeSessionId2(sessionId)}.votes-hint.json`
23275
+ );
23276
+ }
23277
+ function getVotesNudgeMarkerPath(sessionId) {
23278
+ return path71.join(
23279
+ getUserHome(),
23280
+ ".teamai",
23281
+ "sessions",
23282
+ `${sanitizeSessionId2(sessionId)}.votes-nudged.json`
23283
+ );
23284
+ }
23213
23285
  function defaultState() {
23214
23286
  return {
23215
23287
  contributed: false
@@ -23556,6 +23628,33 @@ async function contributeCheck(toolArg) {
23556
23628
  process.stdout.write(formatStopHookOutput2(hint, toolArg ?? "claude"));
23557
23629
  }
23558
23630
  }
23631
+ async function stashVotesHint(sessionId, hintText) {
23632
+ try {
23633
+ await writeJsonAtomic(getPendingVotesHintPath(sessionId), { hintText });
23634
+ } catch (e) {
23635
+ log.error(`Failed to write pending votes hint: ${e.message}`);
23636
+ }
23637
+ }
23638
+ async function takePendingVotesHint(sessionId) {
23639
+ const sidecarPath = getPendingVotesHintPath(sessionId);
23640
+ const pending = await readJson(sidecarPath);
23641
+ if (!pending || typeof pending.hintText !== "string" || !pending.hintText) return null;
23642
+ await fs22.promises.unlink(sidecarPath).catch(() => void 0);
23643
+ return pending.hintText;
23644
+ }
23645
+ async function claimVotesNudge(sessionId) {
23646
+ const markerPath = getVotesNudgeMarkerPath(sessionId);
23647
+ try {
23648
+ await ensureDir(path71.dirname(markerPath));
23649
+ await fs22.promises.writeFile(markerPath, "{}\n", { encoding: "utf-8", flag: "wx" });
23650
+ await cleanupStaleSessions(path71.dirname(markerPath), sessionId).catch(() => void 0);
23651
+ return true;
23652
+ } catch (e) {
23653
+ if (e.code === "EEXIST") return false;
23654
+ log.error(`Failed to claim votes nudge: ${e.message}`);
23655
+ return false;
23656
+ }
23657
+ }
23559
23658
  async function markContributed(sessionId) {
23560
23659
  const state = await readContributeState(sessionId);
23561
23660
  const updated = { ...state, contributed: true };
@@ -23604,18 +23703,19 @@ async function parseTranscriptForVotes(transcriptPath) {
23604
23703
  const trimmed = line.trim();
23605
23704
  if (!trimmed) continue;
23606
23705
  extractRecalledDocIdsFromComment(trimmed, recalledSet);
23607
- if (!trimmed.includes('"assistant"')) continue;
23608
23706
  let entry;
23609
23707
  try {
23610
23708
  entry = JSON.parse(trimmed);
23611
23709
  } catch {
23612
23710
  continue;
23613
23711
  }
23614
- if (entry["type"] !== "assistant") continue;
23615
23712
  const message = entry["message"];
23616
23713
  if (!message || !Array.isArray(message["content"])) continue;
23617
23714
  for (const block of message["content"]) {
23618
- if (block["type"] !== "text") continue;
23715
+ if (block["type"] === "tool_result") {
23716
+ extractRecalledDocIdsFromValue(block["content"], recalledSet);
23717
+ }
23718
+ if (entry["type"] !== "assistant" || block["type"] !== "text") continue;
23619
23719
  const text = block["text"];
23620
23720
  if (typeof text !== "string") continue;
23621
23721
  extractRecalledDocIds(text, recalledSet);
@@ -23631,7 +23731,7 @@ function isValidDocId(docId) {
23631
23731
  return docId.length > 0 && !/[<>]/.test(docId) && docId !== "...";
23632
23732
  }
23633
23733
  function extractRecalledDocIdsFromComment(text, out) {
23634
- const pattern = /<!--\s*teamai:recalled-doc-ids:\s*\[([^\]]*)\]\s*-->/g;
23734
+ const pattern = /(?:<!--|<!—)\s*teamai:recalled-doc-ids:\s*\[([^\]]*)\]\s*(?:-->|—>)/gi;
23635
23735
  let match;
23636
23736
  while ((match = pattern.exec(text)) !== null) {
23637
23737
  for (const item of match[1].split(",")) {
@@ -23640,6 +23740,22 @@ function extractRecalledDocIdsFromComment(text, out) {
23640
23740
  }
23641
23741
  }
23642
23742
  }
23743
+ function extractRecalledDocIdsFromValue(value, out) {
23744
+ if (typeof value === "string") {
23745
+ extractRecalledDocIdsFromComment(value, out);
23746
+ extractRecalledDocIds(value, out);
23747
+ return;
23748
+ }
23749
+ if (Array.isArray(value)) {
23750
+ for (const item of value) extractRecalledDocIdsFromValue(item, out);
23751
+ return;
23752
+ }
23753
+ if (value !== null && typeof value === "object") {
23754
+ for (const item of Object.values(value)) {
23755
+ extractRecalledDocIdsFromValue(item, out);
23756
+ }
23757
+ }
23758
+ }
23643
23759
  function extractRecalledDocIds(text, out) {
23644
23760
  const START = "--- [teamai:recall:start] ---";
23645
23761
  const END = "--- [teamai:recall:end] ---";
@@ -23662,7 +23778,7 @@ function extractRecalledDocIds(text, out) {
23662
23778
  }
23663
23779
  }
23664
23780
  function extractReferencedDocIds(text, out) {
23665
- const pattern = /<!--\s*teamai:referenced-doc-ids:\s*\[([^\]]*)\]\s*-->/g;
23781
+ const pattern = /(?:<!--|<!—)\s*teamai:referenced-doc-ids:\s*\[([^\]]*)\]\s*(?:-->|—>)/gi;
23666
23782
  let match;
23667
23783
  while ((match = pattern.exec(text)) !== null) {
23668
23784
  const raw = match[1];
@@ -24194,14 +24310,16 @@ var init_hook_handlers = __esm({
24194
24310
  async execute(stdin, tool) {
24195
24311
  const { STOP_STDOUT_UNSUPPORTED_TOOLS: STOP_STDOUT_UNSUPPORTED_TOOLS2 } = await Promise.resolve().then(() => (init_tool_names(), tool_names_exports));
24196
24312
  if (!STOP_STDOUT_UNSUPPORTED_TOOLS2.has(tool)) return null;
24197
- const { takePendingHint: takePendingHint2 } = await Promise.resolve().then(() => (init_contribute_check(), contribute_check_exports));
24313
+ const { takePendingHint: takePendingHint2, takePendingVotesHint: takePendingVotesHint2 } = await Promise.resolve().then(() => (init_contribute_check(), contribute_check_exports));
24198
24314
  const sessionId = deriveSessionId(stdin, { includeCwd: true });
24199
24315
  const hint = await takePendingHint2(sessionId);
24200
- if (!hint) return null;
24316
+ const votesHint = await takePendingVotesHint2(sessionId);
24317
+ const combined = [hint, votesHint].filter(Boolean).join("\n");
24318
+ if (!combined) return null;
24201
24319
  return JSON.stringify({
24202
24320
  hookSpecificOutput: {
24203
24321
  hookEventName: "UserPromptSubmit",
24204
- additionalContext: hint
24322
+ additionalContext: combined
24205
24323
  }
24206
24324
  });
24207
24325
  }
@@ -24218,11 +24336,13 @@ var init_hook_handlers = __esm({
24218
24336
  const { autoDetectInit: autoDetectInit2 } = await Promise.resolve().then(() => (init_config(), config_exports));
24219
24337
  const voteData = await parseTranscriptForVotes2(transcriptPath);
24220
24338
  const { localConfig } = await autoDetectInit2();
24221
- const { VOTES_LOCAL_DIR: VOTES_LOCAL_DIR2, TEAMAI_SESSIONS_DIR: TEAMAI_SESSIONS_DIR2 } = await Promise.resolve().then(() => (init_types(), types_exports));
24339
+ const { VOTES_LOCAL_DIR: VOTES_LOCAL_DIR2 } = await Promise.resolve().then(() => (init_types(), types_exports));
24222
24340
  const votesDir = VOTES_LOCAL_DIR2;
24223
24341
  const votePath = path75.join(votesDir, `${localConfig.username}.yaml`);
24224
- if (voteData.referencedDocIds.length > 0) {
24225
- await incrementUpvoted2(votePath, voteData.referencedDocIds);
24342
+ const recalledSet = new Set(voteData.recalledDocIds);
24343
+ const verifiedDocIds = voteData.referencedDocIds.filter((id) => recalledSet.has(id));
24344
+ if (verifiedDocIds.length > 0) {
24345
+ await incrementUpvoted2(votePath, verifiedDocIds);
24226
24346
  }
24227
24347
  if (localConfig.repo.kind === "self") {
24228
24348
  try {
@@ -24243,24 +24363,10 @@ var init_hook_handlers = __esm({
24243
24363
  const declared = voteData.referencedDocIds;
24244
24364
  let nudged = false;
24245
24365
  if (recalled.length > 0 && declared.length === 0) {
24246
- const fsp = await import("fs/promises");
24247
- const safeId = sessionId.replace(/[^a-zA-Z0-9_.-]/g, "_");
24248
- const marker = path75.join(TEAMAI_SESSIONS_DIR2, `${safeId}-adoption-nudged`);
24249
- let already = false;
24250
- try {
24251
- await fsp.access(marker);
24252
- already = true;
24253
- } catch {
24254
- already = false;
24255
- }
24256
- if (!already) {
24257
- try {
24258
- const { ensureDir: ensureDir3 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
24259
- await ensureDir3(TEAMAI_SESSIONS_DIR2);
24260
- await fsp.writeFile(marker, "");
24261
- nudged = true;
24262
- } catch {
24263
- }
24366
+ nudged = true;
24367
+ if ((tool ?? "").toLowerCase() === "cursor") {
24368
+ const { claimVotesNudge: claimVotesNudge2 } = await Promise.resolve().then(() => (init_contribute_check(), contribute_check_exports));
24369
+ nudged = await claimVotesNudge2(sessionId);
24264
24370
  }
24265
24371
  }
24266
24372
  if (process.env.TEAMAI_ADOPTION_EVAL_LOG) {
@@ -24281,7 +24387,13 @@ var init_hook_handlers = __esm({
24281
24387
  }
24282
24388
  if (nudged) {
24283
24389
  const { formatStopHookOutput: formatStopHookOutput2 } = await Promise.resolve().then(() => (init_hook_output(), hook_output_exports));
24390
+ const { STOP_STDOUT_UNSUPPORTED_TOOLS: STOP_STDOUT_UNSUPPORTED_TOOLS2 } = await Promise.resolve().then(() => (init_tool_names(), tool_names_exports));
24284
24391
  const msg = `\u4F60\u672C\u6B21\u901A\u8FC7 teamai \u53EC\u56DE\u4E86\u56E2\u961F\u77E5\u8BC6\uFF08\u5019\u9009 doc-id\uFF1A${recalled.join(", ")}\uFF09\u3002\u7ED3\u675F\u524D\u8BF7\u5728\u56DE\u590D\u672B\u5C3E\u58F0\u660E\u4F60\u5B9E\u9645\u7528\u5230\u7684\u6761\u76EE\uFF1A<!-- teamai:referenced-doc-ids: [\u7528\u5230\u7684doc-id] -->\uFF1B\u6CA1\u7528\u5230\u5C31\u7559\u7A7A []\u3002`;
24392
+ if (STOP_STDOUT_UNSUPPORTED_TOOLS2.has(tool ?? "")) {
24393
+ const { stashVotesHint: stashVotesHint2 } = await Promise.resolve().then(() => (init_contribute_check(), contribute_check_exports));
24394
+ await stashVotesHint2(sessionId, msg);
24395
+ return null;
24396
+ }
24285
24397
  return formatStopHookOutput2(msg, tool ?? "claude");
24286
24398
  }
24287
24399
  } catch {
@@ -24973,7 +25085,7 @@ var init_graph_index_schema = __esm({
24973
25085
  // src/code-knowledge-recall.ts
24974
25086
  import { readFile as readFile4, readdir } from "fs/promises";
24975
25087
  import path79 from "path";
24976
- import matter6 from "gray-matter";
25088
+ import matter7 from "gray-matter";
24977
25089
  function countOccurrences(text, token) {
24978
25090
  let count = 0;
24979
25091
  let idx = 0;
@@ -25225,7 +25337,7 @@ async function loadPagesRecursive(dir, relativePath, pages, depth, currentDepth
25225
25337
  let title;
25226
25338
  let sources;
25227
25339
  try {
25228
- const { data } = matter6(content);
25340
+ const { data } = matter7(content);
25229
25341
  title = typeof data.title === "string" && data.title.trim() ? data.title.trim() : entry.name.replace(".md", "");
25230
25342
  const raw = data.source;
25231
25343
  if (Array.isArray(raw)) {
@@ -27333,7 +27445,7 @@ var init_mr_fetch4 = __esm({
27333
27445
  // src/utils/dedup.ts
27334
27446
  import fs29 from "fs/promises";
27335
27447
  import path85 from "path";
27336
- import matter7 from "gray-matter";
27448
+ import matter8 from "gray-matter";
27337
27449
  function extractKeywords(text) {
27338
27450
  const keywords = /* @__PURE__ */ new Set();
27339
27451
  const enWords = text.match(/[a-zA-Z]+/g) ?? [];
@@ -27397,7 +27509,7 @@ async function findSupersededLearnings(draftKeywords, learningsDir, withinDays =
27397
27509
  continue;
27398
27510
  }
27399
27511
  const raw = await fs29.readFile(filePath, "utf8");
27400
- const { content: body } = matter7(raw);
27512
+ const { content: body } = matter8(raw);
27401
27513
  const fileKeywords = extractKeywords(body);
27402
27514
  const ratio = overlapRatio(draftKeywords, fileKeywords);
27403
27515
  if (ratio >= 0.6) {
@@ -27474,7 +27586,7 @@ var init_dedup = __esm({
27474
27586
  import fs30 from "fs/promises";
27475
27587
  import path86 from "path";
27476
27588
  import readline5 from "readline/promises";
27477
- import matter8 from "gray-matter";
27589
+ import matter9 from "gray-matter";
27478
27590
  async function fetchMR(url) {
27479
27591
  if (url.includes("github.com")) {
27480
27592
  return fetchGitHubPR(url);
@@ -27543,7 +27655,7 @@ function parseLearningDraft(raw) {
27543
27655
  content = content.slice(frontmatterStart);
27544
27656
  }
27545
27657
  try {
27546
- const parsed = matter8(content);
27658
+ const parsed = matter9(content);
27547
27659
  return { content, data: parsed.data };
27548
27660
  } catch (err) {
27549
27661
  const message = err instanceof Error ? err.message : String(err);
@@ -27664,7 +27776,7 @@ var init_import_mr = __esm({
27664
27776
  import { execSync as execSync6 } from "child_process";
27665
27777
  import fs31 from "fs";
27666
27778
  import path87 from "path";
27667
- import matter9 from "gray-matter";
27779
+ import matter10 from "gray-matter";
27668
27780
  async function gatherRepoContext(repoPath) {
27669
27781
  const parts = [];
27670
27782
  try {
@@ -27790,7 +27902,7 @@ ${lines.join("\n")}`);
27790
27902
  try {
27791
27903
  const filePath = path87.join(learningsDir, entry);
27792
27904
  const raw = fs31.readFileSync(filePath, "utf-8");
27793
- const parsed = matter9(raw);
27905
+ const parsed = matter10(raw);
27794
27906
  const tags = parsed.data["tags"];
27795
27907
  if (Array.isArray(tags)) {
27796
27908
  for (const tag of tags) {
@@ -27830,15 +27942,7 @@ function buildFrontmatter(repoPath) {
27830
27942
  ].join("\n");
27831
27943
  }
27832
27944
  function stripExistingFrontmatter(md) {
27833
- if (!md.startsWith("---\n")) {
27834
- return md;
27835
- }
27836
- const secondDash = md.indexOf("\n---\n", 4);
27837
- if (secondDash === -1) {
27838
- return md;
27839
- }
27840
- const afterFrontmatter = md.slice(secondDash + 5);
27841
- return afterFrontmatter.replace(/^\n+/, "");
27945
+ return stripFrontmatter(md);
27842
27946
  }
27843
27947
  async function generateCodebaseMd(opts) {
27844
27948
  const { repoPath, existingCodebaseMd, learningsSuggestions, learningsDir } = opts;
@@ -27968,6 +28072,7 @@ var init_codebase = __esm({
27968
28072
  "use strict";
27969
28073
  init_ai_client();
27970
28074
  init_git2();
28075
+ init_frontmatter();
27971
28076
  init_logger();
27972
28077
  FILE_TREE_MAX_CHARS = 5e3;
27973
28078
  DOC_MAX_CHARS = 2e3;
@@ -33032,14 +33137,14 @@ async function importFromRepo(opts) {
33032
33137
  if (codebaseMd) {
33033
33138
  const overviewPath = path101.join(evidenceDest, "overview.md");
33034
33139
  const existing = await fs35.readFile(overviewPath, "utf8").catch(() => "");
33035
- const aiNarrative = codebaseMd.replace(/^---[\s\S]*?---\n*/m, "");
33140
+ const aiNarrative = stripFrontmatter(codebaseMd);
33036
33141
  const marker = "## AI Architecture Narrative";
33037
33142
  const oldMarker = "## AI \u67B6\u6784\u53D9\u4E8B";
33038
33143
  let markerIdx = existing.indexOf(marker);
33039
33144
  if (markerIdx < 0) markerIdx = existing.indexOf(oldMarker);
33040
33145
  const base = markerIdx >= 0 ? existing.slice(0, markerIdx).trimEnd() : existing.trimEnd();
33041
33146
  let combined;
33042
- if (!base || !base.startsWith("---")) {
33147
+ if (!base || !hasFrontmatter(base)) {
33043
33148
  combined = `---
33044
33149
  title: ${slug} overview
33045
33150
  domain: code-knowledge
@@ -33175,6 +33280,7 @@ var init_import_repo = __esm({
33175
33280
  init_repo_cache();
33176
33281
  init_cache_index();
33177
33282
  init_logger();
33283
+ init_frontmatter();
33178
33284
  }
33179
33285
  });
33180
33286
 
@@ -34261,7 +34367,7 @@ __export(codebase_upgrade_wiki_exports, {
34261
34367
  import { readdir as readdir7, readFile as readFile13 } from "fs/promises";
34262
34368
  import path108 from "path";
34263
34369
  import chalk5 from "chalk";
34264
- import matter10 from "gray-matter";
34370
+ import matter11 from "gray-matter";
34265
34371
  async function upgradeCodebaseWiki(opts) {
34266
34372
  const teamCodebaseDir = path108.join(opts.cwd, "docs", "team-codebase", "repos");
34267
34373
  if (!await pathExists(teamCodebaseDir)) {
@@ -34291,7 +34397,7 @@ async function upgradeCodebaseWiki(opts) {
34291
34397
  const filePath = path108.join(teamCodebaseDir, file);
34292
34398
  try {
34293
34399
  const content = await readFile13(filePath, "utf-8");
34294
- const parsed = matter10(content);
34400
+ const parsed = matter11(content);
34295
34401
  const source = parsed.data["source"] ?? parsed.data["repo_url"];
34296
34402
  if (!source) {
34297
34403
  result.skipped.push(`${slug}: \u65E0 source/repo_url \u5B57\u6BB5`);
@@ -34727,6 +34833,7 @@ function escapeRegex(str) {
34727
34833
  var init_section_patcher = __esm({
34728
34834
  "src/section-patcher.ts"() {
34729
34835
  "use strict";
34836
+ init_frontmatter();
34730
34837
  }
34731
34838
  });
34732
34839
 
@@ -35691,7 +35798,7 @@ var init_extract_mr = __esm({
35691
35798
 
35692
35799
  // src/maintenance/prune.ts
35693
35800
  import path113 from "path";
35694
- import matter11 from "gray-matter";
35801
+ import matter12 from "gray-matter";
35695
35802
  async function findPruneCandidates(learningsDir, votesDir, options = {}) {
35696
35803
  const threshold = options.threshold ?? DEFAULT_THRESHOLD;
35697
35804
  const confidenceMap = await computeAllConfidence(votesDir);
@@ -35706,7 +35813,7 @@ async function findPruneCandidates(learningsDir, votesDir, options = {}) {
35706
35813
  if (!content) continue;
35707
35814
  let date = "";
35708
35815
  try {
35709
- const { data } = matter11(content);
35816
+ const { data } = matter12(content);
35710
35817
  date = typeof data.date === "string" ? data.date : "";
35711
35818
  } catch {
35712
35819
  continue;
@@ -35912,7 +36019,7 @@ var init_quality_update = __esm({
35912
36019
 
35913
36020
  // src/maintenance/promote.ts
35914
36021
  import path115 from "path";
35915
- import matter12 from "gray-matter";
36022
+ import matter13 from "gray-matter";
35916
36023
  async function findPromotionCandidates(learningsDir, votesDir) {
35917
36024
  const confidenceMap = await computeAllConfidence(votesDir);
35918
36025
  const candidates = [];
@@ -35934,7 +36041,7 @@ async function findPromotionCandidates(learningsDir, votesDir) {
35934
36041
  let title = docId;
35935
36042
  let date = "";
35936
36043
  try {
35937
- const { data } = matter12(content);
36044
+ const { data } = matter13(content);
35938
36045
  title = typeof data.title === "string" ? data.title : docId;
35939
36046
  date = typeof data.date === "string" ? data.date : "";
35940
36047
  if (data.promoted_to) continue;
@@ -36014,9 +36121,9 @@ async function executePromotion(candidate, repoPath, options = {}) {
36014
36121
  }
36015
36122
  const promotedContent = await generatePromotedContent(originalContent, category, candidate.title);
36016
36123
  await writeFile(targetPath, promotedContent);
36017
- const { data, content: body } = matter12(originalContent);
36124
+ const { data, content: body } = matter13(originalContent);
36018
36125
  data.promoted_to = `${category}/${candidate.filename}`;
36019
- const updated = matter12.stringify(body, data);
36126
+ const updated = matter13.stringify(body, data);
36020
36127
  await writeFile(candidate.path, updated);
36021
36128
  log.success(`Promoted: ${candidate.docId} -> ${category}/${candidate.filename}`);
36022
36129
  return targetPath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamai-cli",
3
- "version": "0.22.0-beta.2",
3
+ "version": "0.22.0-beta.3",
4
4
  "description": "TeamAI — the team harness for AI agents (skill sync + shared knowledge base, powered by Git)",
5
5
  "type": "module",
6
6
  "bin": {