@fx-data/ai-diagnostics 0.1.20261026070913 → 0.1.20261027064707
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/AGENT_SETUP.md +7 -4
- package/README.md +5 -5
- package/bin/core-ai.mjs +224 -8
- package/content/AGENTS.md +3 -1
- package/content/README.md +2 -0
- package/content/docs/index.md +1 -0
- package/content/docs/npm-cli.md +12 -14
- package/content/docs/roadmap.md +1 -1
- package/content-manifest.json +8 -8
- package/package.json +1 -1
package/AGENT_SETUP.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FX 数据诊断:Agent 安装与管理
|
|
2
2
|
|
|
3
|
-
本文供执行安装与管理的 Agent 读取,不作为日常诊断的前置上下文。包版本为 `0.1.
|
|
3
|
+
本文供执行安装与管理的 Agent 读取,不作为日常诊断的前置上下文。包版本为 `0.1.20261027064707`,需要 Node.js `>=22` 及 npm。用户只需表达安装、使用、更新或卸载意图,环境准备和客户端登记由 Agent 完成。
|
|
4
4
|
|
|
5
5
|
用户明确要求安装、更新、回退、卸载或检查 FX 数据诊断时,也会触发薄 Skill,并直接执行对应管理命令,不先运行 `context` 或读取诊断规则;只有开始诊断时才按薄 Skill 加载内容。普通聊天不主动加载。
|
|
6
6
|
|
|
@@ -54,15 +54,17 @@
|
|
|
54
54
|
|
|
55
55
|
## 按需使用
|
|
56
56
|
|
|
57
|
-
用户明确要求用 FX 数据诊断分析问题后,按薄 Skill
|
|
57
|
+
用户明确要求用 FX 数据诊断分析问题后,按薄 Skill 调用一次稳定入口的 `context`,读取返回的 `agent_rules`,只加载当前问题需要的 Skill 和引用。CLI 同时在后台检查更新,固定本次调查返回的版本与 `pinned_cli`;同一调查继续复用,不重复调用稳定入口的 `context`。普通聊天和管理请求无需预加载诊断内容或检查更新。
|
|
58
58
|
|
|
59
59
|
已有 SigNoZ MCP 直接复用;实时查询受阻时才按包内配置 Skill 处理,凭据只保存在本机。Python 解析按需准备;独立解释器可通过 `FX_DATA_DIAGNOSTICS_PYTHON` 指定绝对路径。
|
|
60
60
|
|
|
61
61
|
## 更新与回退
|
|
62
62
|
|
|
63
|
-
用户要求更新时,调用稳定入口的 `update`;要求回退到上次更新前时调用 `rollback`,明确指定版本时调用 `update --version
|
|
63
|
+
用户要求更新时,调用稳定入口的 `update`;要求回退到上次更新前时调用 `rollback`,明确指定版本时调用 `update --version <版本号>`。新调查使用切换后的版本,进行中的调查保持原版本。回退不锁定后续版本,下一次新诊断仍检查最新版;需要持续使用旧版时按配置变量关闭后台更新。新增客户端只登记,不隐式更新。
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
使用时的后台更新由 CLI 完成,无需 Agent 设置定时任务或另跑 `update`。通知读取、去重和提示按 [安装副本的助手规则](content/AGENTS.md#安装副本的助手规则)执行,不轮询等待。
|
|
66
|
+
|
|
67
|
+
旧版用户需先手动升级一次才能获得后台更新能力。版本切换后按客户端要求刷新技能或新开聊天。
|
|
66
68
|
|
|
67
69
|
若旧试用版提示需要迁移,先用当前 npm 包替换全局程序,直接调用新包 CLI 的 `update`,再登记与检查。迁移保留客户端登记、诊断材料和独立 MCP 配置;旧试用版不作为回退目标。
|
|
68
70
|
|
|
@@ -83,5 +85,6 @@ CLI 使用首次安装记录的 Node/npm 和 prefix,在无网络模式下清
|
|
|
83
85
|
| `FX_DATA_DIAGNOSTICS_HOME` | 自定义共享安装目录 |
|
|
84
86
|
| `FX_DATA_DIAGNOSTICS_PYTHON` | 自定义 Python 可执行文件 |
|
|
85
87
|
| `FX_DATA_DIAGNOSTICS_AGENT_HOME` | 自定义客户端用户目录,通常无需设置 |
|
|
88
|
+
| `FX_DATA_DIAGNOSTICS_AUTO_UPDATE` | 默认开启后台更新;设为 `0` 可关闭,适用于受限环境或需要持续使用旧版时 |
|
|
86
89
|
|
|
87
90
|
显式 CLI 参数优先,其次是环境变量,未指定时使用默认目录或自动发现的运行环境。
|
package/README.md
CHANGED
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
|
|
23
23
|
## 更新
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
每次开始新诊断时,工具会在后台检查更新,不等待更新即可排查。更新成功后,各 Agent 的下一次新诊断使用新版;当前调查保持原版本,更新失败也不影响排查。Agent 会随后续使用提示更新状态,没有新版时不打扰你。
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
需要立即更新并等待完成时说:
|
|
28
28
|
|
|
29
|
-
>
|
|
29
|
+
> 更新 FX 数据诊断。
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
旧版用户先手动更新一次,即可获得后台更新能力。若新版有问题,可以说“把 FX 数据诊断回退到这次更新前的版本”;需要持续使用旧版时,同时让 Agent 关闭后台更新。
|
|
32
32
|
|
|
33
33
|
## 卸载
|
|
34
34
|
|
|
@@ -38,4 +38,4 @@
|
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
**给 Agent:** 按 [Agent 执行说明](https://unpkg.com/@fx-data/ai-diagnostics@0.1.
|
|
41
|
+
**给 Agent:** 按 [Agent 执行说明](https://unpkg.com/@fx-data/ai-diagnostics@0.1.20261027064707/AGENT_SETUP.md) 完成安装与管理。已有本地包时,优先读取包内 `AGENT_SETUP.md`;本地 `.tgz` 只需读取 `package/AGENT_SETUP.md`。按命令结果简短反馈,成功即结束;只有失败或结果不完整时才排错。
|
package/bin/core-ai.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { createHash, randomUUID } from 'node:crypto';
|
|
7
|
-
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
9
|
|
|
10
10
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -29,7 +29,7 @@ function argumentsFor(argv) {
|
|
|
29
29
|
if (args[i] === '--activate-package') {
|
|
30
30
|
if (command !== 'update') throw new Error('--activate-package is reserved for the update subprocess');
|
|
31
31
|
options.activatePackage = true;
|
|
32
|
-
} else if (['--home', '--agent-home', '--agent', '--skills-dir', '--registry', '--version'].includes(args[i])) {
|
|
32
|
+
} else if (['--home', '--agent-home', '--agent', '--skills-dir', '--registry', '--version', '--background-job'].includes(args[i])) {
|
|
33
33
|
if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error(`Missing value for ${args[i]}`);
|
|
34
34
|
options[args[i].slice(2)] = args[++i];
|
|
35
35
|
} else rest.push(args[i]);
|
|
@@ -183,8 +183,12 @@ ${powershell} context
|
|
|
183
183
|
|
|
184
184
|
后续管理命令使用同一组 Node 和稳定脚本路径,将 context 替换为对应子命令,不依赖 PATH 中的全局 CLI 版本。
|
|
185
185
|
|
|
186
|
+
每次新诊断只调用一次稳定入口 context:立即取得并固定本次版本,同时后台检查更新,不等待网络。同一调查继续复用首次结果,不重复调用稳定入口 context;需要重新读取固定路径时使用 pinned_cli context,不触发新检查。
|
|
187
|
+
|
|
186
188
|
context 返回当前版本、内容路径和 agent_rules。读取 agent_rules,按问题只读取所需 Skill 和引用,不预加载全部知识,也不切换用户工作区。包内仓库相对路径以 content_root 为基准,文档链接相对引用文件解析。
|
|
187
189
|
|
|
190
|
+
context、status、session 的 auto_update.notification,以及 python/metric 标准错误中 fx_data_diagnostics.auto_update.notification,是更新提示。有提示时按 message 简短告知用户;无新版不打扰。不为获取状态额外调用 CLI 或轮询;本轮没有后续 CLI 调用时,下次使用再补报。更新成功只影响下次新诊断,失败继续使用本次版本。
|
|
191
|
+
|
|
188
192
|
当前调查固定使用首次 context 返回的 node、pinned_cli 和内容版本。脚本通过 <node> <pinned_cli> python <脚本绝对路径> ... 执行;Windows PowerShell 同样在带引号的 Node 路径前加 &。Metric 调用先读取 analyze-engine-metric 的输入表:纯文本用 metric --metric;SigNoZ JSON 用 metric --metric-log-json <文件> --task-name <选定 task>。通过 session 获取临时目录,日志和产物不写入安装内容。只有解析脚本需要 Python;doctor 的 python.available=false 表示按需补齐,不代表安装失败。已有 MCP 直接复用;实时查询受阻时才读取 setup-signoz-mcp。
|
|
189
193
|
|
|
190
194
|
用户要求更新时执行稳定入口 update,默认使用 latest;要求回退到上次更新前时执行 rollback,也可用 --version 指定版本。已保留版本校验通过后直接复用。各客户端的新调查共用切换后的版本,进行中调查继续使用原 pinned_cli。更新后按客户端要求刷新技能或新开聊天;安装副本不做 Git 更新。
|
|
@@ -362,6 +366,7 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
362
366
|
const { pkg, manifest } = verifyBundle(sourceRoot, { allowMacMetadata: isInstalledBundle(sourceRoot, home) });
|
|
363
367
|
noSymlinkAncestors(home);
|
|
364
368
|
const prior = ownedState(home, false);
|
|
369
|
+
if (options['background-job']) assertBackgroundJob(home, options['background-job'], pkg.version);
|
|
365
370
|
if (!prior && fs.existsSync(home) && fs.readdirSync(home).length) throw new Error('Refusing to take over a nonempty installation directory');
|
|
366
371
|
const ownership = prior?.ownership || { schema_version: 1, home, install_id: randomUUID() };
|
|
367
372
|
const registrationOnly = Boolean(action === 'install' && prior?.state?.lifecycle_protocol === lifecycleProtocol);
|
|
@@ -387,9 +392,13 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
387
392
|
if (!prior) atomicWrite(path.join(home, homeMarker), JSON.stringify(ownership));
|
|
388
393
|
const releaseLock = acquireInstallLock(home);
|
|
389
394
|
let backup = [];
|
|
395
|
+
const launcherBackup = [];
|
|
390
396
|
const release = path.join(home, 'releases', pkg.version);
|
|
391
397
|
const stage = `${release}.${randomUUID()}.tmp`;
|
|
392
398
|
try {
|
|
399
|
+
// A download may outlive its worker or a manual version change. Recheck at
|
|
400
|
+
// activation, under the installation lock, before touching shared entries.
|
|
401
|
+
if (options['background-job']) assertBackgroundJob(home, options['background-job'], pkg.version);
|
|
393
402
|
backup = writeDirectories.map(target => ({ ...target, existed: fs.existsSync(target.directory), text: fs.existsSync(path.join(target.directory, 'SKILL.md')) ? fs.readFileSync(path.join(target.directory, 'SKILL.md')) : null, owner: fs.existsSync(path.join(target.directory, ownerFile)) ? fs.readFileSync(path.join(target.directory, ownerFile)) : null }));
|
|
394
403
|
if (fs.existsSync(release)) {
|
|
395
404
|
verifyBundle(release, { allowMacMetadata: Boolean(prior?.state) });
|
|
@@ -412,7 +421,12 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
412
421
|
[path.join(home, 'bin', commandName), `#!/bin/sh\nexec ${quote(process.execPath)} ${quote(launchers(home).script)} "$@"\n`],
|
|
413
422
|
[path.join(home, 'bin', `${commandName}.cmd`), `@echo off\r\n"${process.execPath}" "%~dp0core-ai.mjs" %*\r\n`],
|
|
414
423
|
]) {
|
|
415
|
-
if (!registrationOnly || !fs.existsSync(file))
|
|
424
|
+
if (!registrationOnly || !fs.existsSync(file)) {
|
|
425
|
+
const existed = fs.existsSync(file);
|
|
426
|
+
launcherBackup.push({ file, existed, text: existed ? fs.readFileSync(file) : null,
|
|
427
|
+
mode: existed ? fs.statSync(file).mode & 0o777 : 0o755 });
|
|
428
|
+
atomicWrite(file, text, 0o755);
|
|
429
|
+
}
|
|
416
430
|
}
|
|
417
431
|
const registrationReady = performance.now();
|
|
418
432
|
const checks = { integrity: { status: 'ok' }, skills: skillChecks(targets, home, ownership.install_id), context: { status: 'ok' } };
|
|
@@ -430,6 +444,9 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
430
444
|
const npmInstallation = prior?.state ? prior.state.npm_installation : detectNpmInstallation(sourceRoot);
|
|
431
445
|
const state = { schema_version: 1, lifecycle_protocol: lifecycleProtocol, release_protocols: releaseProtocols, install_id: ownership.install_id, package: pkg.name, version: pkg.version, previous_version: prior?.state?.version === pkg.version ? prior.state.previous_version : prior?.state?.version || null, registry, skills: targets, source_commit: manifest.source_commit,
|
|
432
446
|
...(npmInstallation ? { npm_installation: npmInstallation } : {}) };
|
|
447
|
+
// Copying and validation can outlast the background deadline. The last
|
|
448
|
+
// check belongs immediately before committing the active version too.
|
|
449
|
+
if (options['background-job']) assertBackgroundJob(home, options['background-job'], pkg.version);
|
|
433
450
|
if (JSON.stringify(state) !== JSON.stringify(prior?.state)) atomicWrite(path.join(home, 'state.json'), JSON.stringify(state, null, 2));
|
|
434
451
|
const finished = performance.now();
|
|
435
452
|
const elapsed = (from, to) => Math.round((to - from) * 100) / 100;
|
|
@@ -440,6 +457,10 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
440
457
|
agent_hint: '安装登记与文件检查已完成。按返回结果用 2–3 句话反馈版本、首次安装或复用、需要的刷新及使用提示后结束;不再运行 status/ls/doctor/context 或读取源码、客户端缓存。本工具不要求安装清单或长期记忆,仍遵循客户端强制规则。客户端发现未验证时只提示刷新或新开聊天。' } : {}),
|
|
441
458
|
refresh: 'Open a new Agent conversation if the skill has not refreshed.' });
|
|
442
459
|
} catch (error) {
|
|
460
|
+
for (const item of launcherBackup) {
|
|
461
|
+
if (item.existed) atomicWrite(item.file, item.text, item.mode);
|
|
462
|
+
else fs.rmSync(item.file, { force: true });
|
|
463
|
+
}
|
|
443
464
|
for (const item of backup) {
|
|
444
465
|
if (item.existed) { atomicWrite(path.join(item.directory, 'SKILL.md'), item.text, 0o644); atomicWrite(path.join(item.directory, ownerFile), item.owner); }
|
|
445
466
|
else if (fs.existsSync(item.directory)) fs.rmSync(item.directory, { recursive: true });
|
|
@@ -565,11 +586,187 @@ function update(home, options, action = 'update') {
|
|
|
565
586
|
for (const key of Object.keys(env)) if (key.toLowerCase() === 'path') delete env[key];
|
|
566
587
|
env.PATH = [path.dirname(process.execPath), inheritedPath].filter(Boolean).join(path.delimiter);
|
|
567
588
|
Object.assign(env, { FX_DATA_DIAGNOSTICS_HOME: home, NPM_CONFIG_USERCONFIG: path.join(npmHome, 'user.npmrc'), NPM_CONFIG_GLOBALCONFIG: path.join(npmHome, 'global.npmrc'), NPM_CONFIG_CACHE: path.join(npmHome, 'cache'), NPM_CONFIG_REGISTRY: registry, NPM_CONFIG_UPDATE_NOTIFIER: 'false' });
|
|
568
|
-
const
|
|
589
|
+
const background = options['background-job'];
|
|
590
|
+
if (background) assertBackgroundJob(home, background, version);
|
|
591
|
+
const result = spawnSync(process.execPath, [npmExecutable(), 'exec', '--yes', '--registry', registry,
|
|
592
|
+
...(background ? ['--fetch-retries=0', '--fetch-timeout=15000', '--ignore-scripts'] : []),
|
|
593
|
+
'--package', `${state.package}@${version}`, '--', commandName, 'update', '--activate-package', '--home', home, '--registry', registry,
|
|
594
|
+
...(background ? ['--background-job', background] : [])], { cwd: npmHome, env, stdio: 'inherit' });
|
|
569
595
|
if (result.error) throw result.error;
|
|
570
596
|
process.exitCode = result.status ?? 1;
|
|
571
597
|
}
|
|
572
598
|
|
|
599
|
+
// Automatic updates belong to the shared installation, not to an Agent. Only
|
|
600
|
+
// a new diagnostic entry starts a worker; pinned scripts merely carry notices.
|
|
601
|
+
const autoUpdateFile = 'auto-update.json';
|
|
602
|
+
const autoUpdateLock = '.auto-update.lock';
|
|
603
|
+
const autoUpdateNotice = 'auto-update-notice.json';
|
|
604
|
+
const autoUpdateTimeout = 60_000;
|
|
605
|
+
const autoUpdateEnabled = () => process.env.FX_DATA_DIAGNOSTICS_AUTO_UPDATE !== '0';
|
|
606
|
+
const readOptional = file => { try { return json(file); } catch { return null; } };
|
|
607
|
+
|
|
608
|
+
function deadProcess(pid) {
|
|
609
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
610
|
+
try { process.kill(pid, 0); } catch (error) { return error.code === 'ESRCH'; }
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function newerVersion(candidate, current) {
|
|
615
|
+
if (!versionPattern.test(candidate) || !versionPattern.test(current)) return false;
|
|
616
|
+
const split = value => { const [core, ...pre] = value.split('-'); return { core: core.split('.').map(BigInt), pre: pre.join('-') }; };
|
|
617
|
+
const next = split(candidate), old = split(current);
|
|
618
|
+
for (let i = 0; i < 3; i++) if (next.core[i] !== old.core[i]) return next.core[i] > old.core[i];
|
|
619
|
+
if (!next.pre || !old.pre) return Boolean(old.pre && !next.pre);
|
|
620
|
+
const left = next.pre.split('.'), right = old.pre.split('.');
|
|
621
|
+
for (let i = 0; i < Math.max(left.length, right.length); i++) {
|
|
622
|
+
if (left[i] === undefined || right[i] === undefined) return right[i] === undefined;
|
|
623
|
+
if (left[i] === right[i]) continue;
|
|
624
|
+
const numericLeft = /^\d+$/.test(left[i]), numericRight = /^\d+$/.test(right[i]);
|
|
625
|
+
if (numericLeft && numericRight) return BigInt(left[i]) > BigInt(right[i]);
|
|
626
|
+
return numericLeft !== numericRight ? !numericLeft : left[i] > right[i];
|
|
627
|
+
}
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function updateJobOwned(home, id) {
|
|
632
|
+
const lock = readOptional(path.join(home, autoUpdateLock));
|
|
633
|
+
return lock?.id === id && lock.hostname === os.hostname();
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function releaseUpdateJob(home, id) {
|
|
637
|
+
try {
|
|
638
|
+
if (updateJobOwned(home, id)) fs.rmSync(path.join(home, autoUpdateLock), { force: true });
|
|
639
|
+
} catch { /* Optional background bookkeeping cannot fail a diagnostic call. */ }
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function saveUpdateJob(home, job, status) {
|
|
643
|
+
if (!updateJobOwned(home, job.id) || ownedState(home, false)?.state?.install_id !== job.install_id) return;
|
|
644
|
+
job.status = status;
|
|
645
|
+
atomicWrite(path.join(home, autoUpdateFile), JSON.stringify(job));
|
|
646
|
+
const message = status === 'updating'
|
|
647
|
+
? `发现 FX 数据诊断新版 ${job.target_version},正在后台更新。本次分析继续使用已固定版本,更新成功后下次诊断生效。`
|
|
648
|
+
: status === 'updated'
|
|
649
|
+
? `FX 数据诊断已后台更新至 ${job.target_version},供下次新诊断使用;进行中的分析仍使用各自已固定的版本。`
|
|
650
|
+
: status === 'failed'
|
|
651
|
+
? 'FX 数据诊断后台检查或更新未完成,继续使用当前可用版本,不影响本次排查。'
|
|
652
|
+
: null;
|
|
653
|
+
if (message) atomicWrite(path.join(home, autoUpdateNotice), JSON.stringify({ id: `${job.id}:${status}`, install_id: job.install_id, status, message }));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function autoUpdateDetails(home) {
|
|
657
|
+
if (!autoUpdateEnabled()) return null;
|
|
658
|
+
try {
|
|
659
|
+
const state = ownedState(home, false)?.state;
|
|
660
|
+
if (!state) return null;
|
|
661
|
+
const job = readOptional(path.join(home, autoUpdateFile));
|
|
662
|
+
const details = job?.install_id === state.install_id
|
|
663
|
+
? { status: job.status, ...(job.target_version ? { target_version: job.target_version } : {}) } : null;
|
|
664
|
+
// Claim one notice without deleting a newer notice written concurrently.
|
|
665
|
+
// Other clients may receive the next state; the worker never writes to a
|
|
666
|
+
// parent's terminal and diagnostic/script stdout keeps its original shape.
|
|
667
|
+
const claimed = path.join(home, `${autoUpdateNotice}.${randomUUID()}.read`);
|
|
668
|
+
try {
|
|
669
|
+
fs.renameSync(path.join(home, autoUpdateNotice), claimed);
|
|
670
|
+
const notice = readOptional(claimed);
|
|
671
|
+
if (notice?.install_id === state.install_id) return { ...details, notification: { id: notice.id, status: notice.status, message: notice.message } };
|
|
672
|
+
} catch { /* No notice, or another CLI already delivered it. */ }
|
|
673
|
+
finally { fs.rmSync(claimed, { force: true }); }
|
|
674
|
+
return details;
|
|
675
|
+
} catch { return null; } // Update bookkeeping must never block diagnosis.
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function assertBackgroundJob(home, id, version) {
|
|
679
|
+
const job = readOptional(path.join(home, autoUpdateFile));
|
|
680
|
+
const state = ownedState(home, false)?.state;
|
|
681
|
+
if (!updateJobOwned(home, id) || job?.id !== id || job.status !== 'updating'
|
|
682
|
+
|| Date.now() >= job.expires_at || deadProcess(job.pid)
|
|
683
|
+
|| !state || state.install_id !== job.install_id || state.version !== job.base_version
|
|
684
|
+
|| version !== job.target_version) throw new Error('Background update is no longer applicable; keeping the current installation');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function startAutoUpdate(home) {
|
|
688
|
+
if (!autoUpdateEnabled()) return;
|
|
689
|
+
let job;
|
|
690
|
+
try {
|
|
691
|
+
const { state } = ownedState(home);
|
|
692
|
+
if (!state) return;
|
|
693
|
+
const lockPath = path.join(home, autoUpdateLock);
|
|
694
|
+
const existing = readOptional(lockPath);
|
|
695
|
+
if (fs.existsSync(lockPath)) {
|
|
696
|
+
// Age alone does not permit stealing a live worker's lock. Failed jobs
|
|
697
|
+
// have no cooldown; after a dead worker the next diagnosis can try again.
|
|
698
|
+
if (existing?.hostname !== os.hostname() || !deadProcess(existing.pid)) return;
|
|
699
|
+
if (readOptional(lockPath)?.id !== existing.id) return;
|
|
700
|
+
fs.unlinkSync(lockPath);
|
|
701
|
+
}
|
|
702
|
+
job = { id: randomUUID(), install_id: state.install_id, base_version: state.version,
|
|
703
|
+
pid: process.pid, hostname: os.hostname(), status: 'checking', expires_at: Date.now() + autoUpdateTimeout };
|
|
704
|
+
const fd = fs.openSync(lockPath, 'wx', 0o600);
|
|
705
|
+
try { fs.writeFileSync(fd, JSON.stringify(job)); } finally { fs.closeSync(fd); }
|
|
706
|
+
saveUpdateJob(home, job, 'checking');
|
|
707
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !/^(NPM_TOKEN|NODE_AUTH_TOKEN)$/i.test(key)));
|
|
708
|
+
const child = spawn(process.execPath, [path.join(home, 'releases', state.version, 'bin/core-ai.mjs'), '__background-update', job.id, '--home', home],
|
|
709
|
+
{ cwd: home, env, detached: true, stdio: 'ignore', windowsHide: true });
|
|
710
|
+
child.on('error', () => {
|
|
711
|
+
try { saveUpdateJob(home, job, 'failed'); } catch { /* Diagnosis still works. */ }
|
|
712
|
+
finally { releaseUpdateJob(home, job.id); }
|
|
713
|
+
});
|
|
714
|
+
if (child.pid) {
|
|
715
|
+
// Hand the live lock to the worker before the launching CLI exits.
|
|
716
|
+
job.pid = child.pid;
|
|
717
|
+
atomicWrite(lockPath, JSON.stringify(job));
|
|
718
|
+
}
|
|
719
|
+
child.unref();
|
|
720
|
+
} catch (error) {
|
|
721
|
+
if (job && updateJobOwned(home, job.id)) {
|
|
722
|
+
try { saveUpdateJob(home, job, 'failed'); } catch { /* Best effort only. */ }
|
|
723
|
+
finally { releaseUpdateJob(home, job.id); }
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
async function backgroundUpdate(home, id) {
|
|
729
|
+
const job = readOptional(path.join(home, autoUpdateFile));
|
|
730
|
+
if (job?.id !== id || !updateJobOwned(home, id)) return;
|
|
731
|
+
job.pid = process.pid;
|
|
732
|
+
try {
|
|
733
|
+
const state = ownedState(home).state;
|
|
734
|
+
if (!state || state.install_id !== job.install_id || state.version !== job.base_version) return;
|
|
735
|
+
saveUpdateJob(home, job, 'checking');
|
|
736
|
+
const registry = registryOrigin(state.registry);
|
|
737
|
+
const response = await fetch(`${registry}/${encodeURIComponent(state.package)}/latest`,
|
|
738
|
+
{ signal: AbortSignal.timeout(3_000), redirect: 'error', headers: { Accept: 'application/json', 'Cache-Control': 'no-cache' } });
|
|
739
|
+
if (!response.ok) throw new Error('Version check failed');
|
|
740
|
+
const latest = await response.json();
|
|
741
|
+
if (latest.name !== state.package || typeof latest.version !== 'string' || !versionPattern.test(latest.version)) throw new Error('Invalid latest version');
|
|
742
|
+
if (!newerVersion(latest.version, state.version)) { saveUpdateJob(home, job, 'current'); return; }
|
|
743
|
+
job.target_version = latest.version;
|
|
744
|
+
saveUpdateJob(home, job, 'updating');
|
|
745
|
+
assertBackgroundJob(home, id, latest.version);
|
|
746
|
+
// Reuse the explicit-version updater in an isolated child. Its output must
|
|
747
|
+
// not keep the initiating Agent tool open or mix with diagnostic results.
|
|
748
|
+
await new Promise((resolve, reject) => {
|
|
749
|
+
const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'update', '--version', latest.version,
|
|
750
|
+
'--home', home, '--background-job', id], { cwd: home, env: process.env, stdio: 'ignore', windowsHide: true });
|
|
751
|
+
const timer = setTimeout(() => {
|
|
752
|
+
// Invalidate activation before stopping the child: npm may have its own
|
|
753
|
+
// subprocess, which must not activate a release after this deadline.
|
|
754
|
+
try { saveUpdateJob(home, job, 'failed'); } catch { /* Best effort. */ }
|
|
755
|
+
child.kill('SIGKILL');
|
|
756
|
+
reject(new Error('Background update timed out'));
|
|
757
|
+
}, Math.max(1, job.expires_at - Date.now()));
|
|
758
|
+
child.once('error', error => { clearTimeout(timer); reject(error); });
|
|
759
|
+
child.once('exit', code => { clearTimeout(timer); code === 0 ? resolve() : reject(new Error('Background update failed')); });
|
|
760
|
+
});
|
|
761
|
+
if (ownedState(home, false)?.state?.version !== latest.version) throw new Error('Version activation was not confirmed');
|
|
762
|
+
saveUpdateJob(home, job, 'updated');
|
|
763
|
+
} catch {
|
|
764
|
+
try { if (job.status !== 'failed') saveUpdateJob(home, job, 'failed'); } catch { /* Never disturb the active release. */ }
|
|
765
|
+
} finally {
|
|
766
|
+
releaseUpdateJob(home, id);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
573
770
|
function python() {
|
|
574
771
|
const configured = process.env.FX_DATA_DIAGNOSTICS_PYTHON;
|
|
575
772
|
const candidates = configured ? [[configured]] : process.platform === 'win32' ? [['py', '-3'], ['python'], ['python3']] : [['python3'], ['python']];
|
|
@@ -595,7 +792,7 @@ function uninstall(home) {
|
|
|
595
792
|
// Remove only owned program files. Configuration, npm cache and evidence are retained.
|
|
596
793
|
// Keep the stable command and state until version cleanup completes, so a
|
|
597
794
|
// client-denied recursive deletion can be confirmed and retried normally.
|
|
598
|
-
for (const name of ['releases', 'bin', 'state.json']) fs.rmSync(path.join(home, name), { recursive: true, force: true });
|
|
795
|
+
for (const name of [autoUpdateLock, autoUpdateFile, autoUpdateNotice, 'releases', 'bin', 'state.json']) fs.rmSync(path.join(home, name), { recursive: true, force: true });
|
|
599
796
|
const finished = performance.now();
|
|
600
797
|
const elapsed = (from, to) => Math.round((to - from) * 100) / 100;
|
|
601
798
|
emit({ ok: true, action: 'uninstall', complete: npmPackage.status !== 'unconfirmed', home,
|
|
@@ -637,6 +834,8 @@ async function main() {
|
|
|
637
834
|
if (help) return emit(help);
|
|
638
835
|
const { command, options, rest } = argumentsFor(process.argv.slice(2));
|
|
639
836
|
const home = normalizedPath(options.home || process.env.FX_DATA_DIAGNOSTICS_HOME || installedBundleHome(packageRoot) || defaultHome());
|
|
837
|
+
if (command === '__background-update') return backgroundUpdate(home, rest[0]);
|
|
838
|
+
if (options['background-job'] && command !== 'update') throw new Error('--background-job is reserved for background updates');
|
|
640
839
|
let current;
|
|
641
840
|
try { current = ownedState(home, false); }
|
|
642
841
|
catch (error) { if (command === 'doctor') return doctor(home); throw error; }
|
|
@@ -676,14 +875,31 @@ async function main() {
|
|
|
676
875
|
if (command === 'uninstall') return uninstall(home);
|
|
677
876
|
const { pkg, manifest, content } = verifyBundle(packageRoot, { allowMacMetadata: isInstalledBundle(packageRoot, home) });
|
|
678
877
|
if (command === '--version' || command === 'version') return emit({ version: pkg.version, package: pkg.name });
|
|
679
|
-
if (command === 'status')
|
|
680
|
-
|
|
681
|
-
|
|
878
|
+
if (command === 'status') {
|
|
879
|
+
const auto = autoUpdateDetails(home);
|
|
880
|
+
return emit({ ok: true, installed: Boolean(current?.state), home, ...current?.state, lifecycle_protocol: current?.state?.lifecycle_protocol ?? null, migration_required: migrationRequired, launcher: current?.state ? launchers(home).executable : null, launcher_script: current?.state ? launchers(home).script : null, node: process.execPath, ...(auto ? { auto_update: auto } : {}) });
|
|
881
|
+
}
|
|
882
|
+
if (command === 'context') {
|
|
883
|
+
// Capture paths before starting the worker. Direct calls to a pinned release
|
|
884
|
+
// are continuations, even if that release is still the current one.
|
|
885
|
+
const context = contextFor(packageRoot, home, pkg, manifest);
|
|
886
|
+
const auto = autoUpdateDetails(home);
|
|
887
|
+
const directPinned = isInstalledBundle(packageRoot, home)
|
|
888
|
+
&& fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
889
|
+
if (current?.state && !directPinned) startAutoUpdate(home);
|
|
890
|
+
return emit({ ...context, ...(auto ? { auto_update: auto } : {}) });
|
|
891
|
+
}
|
|
892
|
+
if (command === 'session') {
|
|
893
|
+
const auto = autoUpdateDetails(home);
|
|
894
|
+
return emit({ directory: fs.mkdtempSync(path.join(os.tmpdir(), 'core-ai-')), ...(auto ? { auto_update: auto } : {}) });
|
|
895
|
+
}
|
|
682
896
|
if (command === 'metric' || command === 'python') {
|
|
683
897
|
const runtime = python();
|
|
684
898
|
if (!runtime.available) throw new Error(runtime.requirement);
|
|
685
899
|
const args = command === 'metric' ? [path.join(content, '.agents/skills/analyze-engine-metric/scripts/analyze_engine_metric.py'), ...rest] : rest;
|
|
686
900
|
if (!args.length) throw new Error('A script or Python option is required');
|
|
901
|
+
const auto = autoUpdateDetails(home);
|
|
902
|
+
if (auto?.notification) process.stderr.write(`${JSON.stringify({ fx_data_diagnostics: { auto_update: auto } })}\n`);
|
|
687
903
|
const result = spawnSync(runtime.executable, ['-I', '-B', ...args], { stdio: 'inherit' });
|
|
688
904
|
if (result.error) throw result.error;
|
|
689
905
|
process.exitCode = result.status ?? 1;
|
package/content/AGENTS.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
只有本项目根目录存在 `.git` 文件或目录时,才按源码仓库方式更新:每次新对话首次使用时,在该根目录运行 `git pull --ff-only`;同一对话不重复更新。保留本地改动,不切换分支。无法安全更新或联网失败时,简短说明并继续原任务。
|
|
8
8
|
|
|
9
|
-
通过 CLI 安装的目录没有 `.git`,不得在它或使用者的其他项目执行 Git 更新。全局 Skill 通过 `context` 返回内容目录时,遵循 [npm 安装副本规则](#安装副本的助手规则)
|
|
9
|
+
通过 CLI 安装的目录没有 `.git`,不得在它或使用者的其他项目执行 Git 更新。全局 Skill 通过 `context` 返回内容目录时,遵循 [npm 安装副本规则](#安装副本的助手规则)。首次使用时读取对应说明,本轮已读则复用。新诊断由 CLI 后台检查更新,当前调查始终使用首次返回的版本。
|
|
10
10
|
|
|
11
11
|
### 安装副本的助手规则
|
|
12
12
|
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
- 安装副本没有 `.git`,跳过 Git 更新检查,不更新用户的其他项目。只读取当前问题需要的 Skill 及引用,不将交付方案全文作为诊断前置知识。
|
|
16
16
|
- 本次调查固定使用首次 `context` 返回的版本、`content_root`、`agent_rules`、`skills_root`、`node` 和 `pinned_cli`;脚本通过 `<node> <pinned_cli> python <脚本路径> ...` 执行。仓库相对路径以 `content_root` 为基准,文档链接相对引用文件解析。
|
|
17
|
+
- 每次新诊断只调用一次稳定入口的 `context`;CLI 在后台检查更新,不等待网络或下载,同一调查继续复用已返回的版本。
|
|
18
|
+
- 更新通知由 CLI 返回:`context`、`status`、`session` 的 JSON 中查看可选的 `auto_update.notification`;`python`、`metric` 的 stderr 可带独立 JSON,其中为 `fx_data_diagnostics.auto_update.notification`,stdout 仍是原脚本结果。通知包含 `id`、`status`、`message`,按 `message` 简短告知,按 `id` 避免重复提示;当前调查保持固定版本,新版供下次新诊断使用。没有通知不提示,不为获取更新结果额外轮询;本轮没有后续 CLI 调用时,下次使用再补报。
|
|
17
19
|
- 用 `session` 获取临时目录。Metric 纯正文使用 `metric --metric`;SigNoZ JSON 响应使用 `metric --metric-log-json ... --task-name ...`,不可混用。
|
|
18
20
|
- 已有可用 MCP 直接复用;实时查询受阻时读取 [MCP 配置 Skill](.agents/skills/setup-signoz-mcp/SKILL.md),适配当前客户端。凭据保存在本机配置,不写入能力包或诊断报告。
|
|
19
21
|
- 用户要求更新、回退、卸载或检查时使用稳定入口的 `update`、`rollback`、`uninstall`、`doctor`。版本切换后按客户端要求刷新技能或新开聊天;当前调查不切换版本。
|
package/content/README.md
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
交付方式见 [FX 数据诊断交付方案](docs/npm-cli.md):用户说“帮我安装并启用 FX 数据诊断,npm 包是 `@fx-data/ai-diagnostics`”,Agent 从包内 README 的 Agent 入口读取安装说明,优先复用已有共享安装,再登记当前客户端的薄 Skill。CLI 使用 Node.js/npm,Python 解析环境按需准备。
|
|
20
20
|
|
|
21
|
+
npm 安装副本在每次新诊断时后台检查更新,当前排查不等待;更新成功后下次新诊断生效,失败继续使用旧版,见[版本管理](docs/npm-cli.md#34-版本管理)。
|
|
22
|
+
|
|
21
23
|
1. 使用源码目录时,用支持 `AGENTS.md` 的 Agent 客户端(如 Codex、Claude Code)打开本仓库,更新规则见 [助手执行契约](AGENTS.md#使用前更新代码);npm 安装副本按交付方案管理版本。
|
|
22
24
|
2. 诊断需要查询 SigNoZ 的 trace、日志和引擎统计。当前客户端还没有 SigNoZ 连接时,助手会自动安装并配置,只需提供对应环境的 API Key。正式环境和测试环境的 Key 分别维护。
|
|
23
25
|
3. 只有离线材料(日志、trace 导出、Metric 文件)时,无需配置连接,直接交给助手分析。
|
package/content/docs/index.md
CHANGED
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
| 非性能案例保留什么、反馈后怎样改进 | [非性能案例](diagnostic-feedback.md#非性能案例怎样保留)、[收到改进请求后](diagnostic-feedback.md#收到改进请求后怎样使用) |
|
|
67
67
|
| Skill 与说明的写法 | [Skill 编写建议](skill-writing-guide.md) |
|
|
68
68
|
| 交付方案选型、用户安装使用、版本更新与发布 | [FX 数据诊断交付方案](npm-cli.md) |
|
|
69
|
+
| 新诊断后台检查更新、失败保留旧版与更新状态提示 | [版本管理](npm-cli.md#34-版本管理)、[安装副本的助手规则](../AGENTS.md#安装副本的助手规则) |
|
|
69
70
|
| npm 交付模块目录、构建与发布职责 | [构建打包](npm-cli.md#32-构建打包)、[发布分发](npm-cli.md#33-发布分发) |
|
|
70
71
|
| npm 包内的用户使用说明 | 包内 README,由 `packages/ai-diagnostics/templates/README.md` 模板生成 |
|
|
71
72
|
| npm 包的 Agent 安装与管理步骤、配置变量 | 包内 `AGENT_SETUP.md`,由 `packages/ai-diagnostics/templates/AGENT_SETUP.md` 模板生成 |
|
package/content/docs/npm-cli.md
CHANGED
|
@@ -106,9 +106,13 @@ flowchart TD
|
|
|
106
106
|
|
|
107
107
|
同一系统账户下,各 Agent 共用一份安装和当前版本。CLI 管理各版本副本,通过固定入口执行更新与回退。新版下载并校验成功后切换,失败保留原可用版本,旧版保留供回退。更新或回退后,各 Agent 的新调查统一使用切换后的版本,进行中的调查保持原版本,避免中途改变诊断规则或脚本。
|
|
108
108
|
|
|
109
|
-
CLI 记录更新前的版本,用户要求回退时,Agent
|
|
109
|
+
CLI 记录更新前的版本,用户要求回退时,Agent 根据记录确定目标版本,无需用户记忆版本号;也支持用户明确指定版本。回退只切换当前版本,不锁定后续版本;下一次新诊断仍检查最新版。需要持续使用旧版或在受限环境中禁止后台联网时,可设置 `FX_DATA_DIAGNOSTICS_AUTO_UPDATE=0` 关闭后台更新,默认开启。
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
**每次开始新诊断时后台检查更新,不设固定检查周期。** 稳定入口的 `context` 立即返回并固定本次调查版本,由独立后台任务检查 npm 最新版本;有新版才下载并校验,成功后切换共享版本。当前调查不等待网络或下载,始终使用已返回的内容和脚本;网络、下载或校验失败时继续使用原可用版本。同一账户的各 Agent 共用后台任务,已有任务时复用,同一调查不重复发起检查;普通聊天和管理命令不触发检查。
|
|
112
|
+
|
|
113
|
+
更新状态随后续 CLI 调用返回,由 Agent 简短提示“发现新版,后台更新,下次诊断生效”“更新完成”或“更新失败,继续使用当前版本”。没有新版不提示,不为等待更新结果增加调用或轮询;本轮没有后续 CLI 调用时,下次使用再补报。后台进程不直接向客户端聊天窗口发送消息。助手执行约定见 [npm 安装副本规则](../AGENTS.md#安装副本的助手规则)。
|
|
114
|
+
|
|
115
|
+
用户仍可明确要求立即更新,由 Agent 调用 `update` 并等待结果。旧版安装需要先手动升级一次,才能获得使用时后台更新的能力。诊断材料独立保存,更新不影响材料,也不替换独立安装的 MCP 及其配置。
|
|
112
116
|
|
|
113
117
|
### 3.5 安装与加载
|
|
114
118
|
|
|
@@ -134,7 +138,7 @@ npm 安装取得 CLI 和完整诊断内容;随后 Agent 只需调用一次 `in
|
|
|
134
138
|
|
|
135
139
|
```text
|
|
136
140
|
读取薄 Skill
|
|
137
|
-
→ 调用 CLI context
|
|
141
|
+
→ 调用 CLI context 固定本次版本与内容路径,同时后台检查更新
|
|
138
142
|
→ 读取 AGENTS.md 和当前问题所需的 Skill、知识
|
|
139
143
|
→ 调用 SigNoZ MCP 查询,或调用包内脚本解析材料
|
|
140
144
|
→ 分析证据并回答
|
|
@@ -175,25 +179,19 @@ Agent 自动判断首次安装或复用已有安装,用户无需指定安装
|
|
|
175
179
|
|
|
176
180
|
### 4.3 版本管理
|
|
177
181
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
> 更新 FX 数据诊断。
|
|
182
|
+
开始新诊断时自动在后台检查更新,不耽误本次排查;Agent 会提示发现新版、更新结果和何时生效,没有新版不打扰用户。
|
|
181
183
|
|
|
182
|
-
|
|
184
|
+
需要立即更新并等待完成时说:
|
|
183
185
|
|
|
184
|
-
>
|
|
185
|
-
|
|
186
|
-
停止自动更新时说:
|
|
187
|
-
|
|
188
|
-
> 取消 FX 数据诊断的自动更新任务。
|
|
186
|
+
> 更新 FX 数据诊断。
|
|
189
187
|
|
|
190
188
|
需要回退时说:
|
|
191
189
|
|
|
192
190
|
> 把 FX 数据诊断回退到这次更新前的版本。
|
|
193
191
|
|
|
194
|
-
|
|
192
|
+
也可以明确指定要回退的版本号。若需持续使用旧版,请同时让 Agent 关闭后台更新。
|
|
195
193
|
|
|
196
|
-
|
|
194
|
+
更新、回退和后台检查机制见[版本管理](#34-版本管理)。旧版用户先手动更新一次,即可获得后台更新能力。
|
|
197
195
|
|
|
198
196
|
### 4.4 卸载
|
|
199
197
|
|
package/content/docs/roadmap.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
每条规则只有一个归属文件,统一登记在[文档目录](index.md)。各 Skill 保留触发条件和引用,经验用于提出业务假设,不另设取证门槛。
|
|
18
18
|
|
|
19
|
-
正式方向见 [FX 数据诊断交付方案](npm-cli.md):统一 npm 包与薄 Skill,优先采用公共 npm
|
|
19
|
+
正式方向见 [FX 数据诊断交付方案](npm-cli.md):统一 npm 包与薄 Skill,优先采用公共 npm 分发;新诊断后台检查更新,当前调查保持版本,更新成功后下次生效,也保留手动更新。当前实现已通过隔离安装生命周期测试及 Docker 构建演练;新版在真实客户端(包括 WorkBuddy)中的安装与使用、Windows 真机、空白环境初始化和公开发布仍待验收。早期 beta 的 macOS WorkBuddy 验证不代表当前实现已验收;CI 尚未接入。
|
|
20
20
|
|
|
21
21
|
## 优先补齐什么
|
|
22
22
|
|
package/content-manifest.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"source_commit": "
|
|
3
|
+
"version": "0.1.20261027064707",
|
|
4
|
+
"source_commit": "1a2b56dabd80096d81ae5dde02034fd85adc33a2",
|
|
5
5
|
"source_dirty": false,
|
|
6
|
-
"cli_sha256": "
|
|
6
|
+
"cli_sha256": "06cd1dfa37a105db668d3a9228ec016fcce22f17bdce6c2611c8d8c4e1e44dc6",
|
|
7
7
|
"files": {
|
|
8
8
|
".agents/skills/analyze-analysis-table-update-runtime/SKILL.md": "6f8b31592d5c4396aaa928c171f24b28ecd675f792c760e2d4aa5749e61a9750",
|
|
9
9
|
".agents/skills/analyze-analysis-table-update-runtime/agents/openai.yaml": "b4802f9423cdc96e911c91a6edbc45e268a5c98bb2a723b8d2776c7d2ee385d2",
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
".agents/skills/setup-signoz-mcp/references/windows-artifact.md": "1e05225befe619eaf2c0beccd392c626755abd3feae9c4867cec1da28b9f44ab",
|
|
36
36
|
".agents/skills/setup-signoz-mcp/scripts/install_signoz_mcp.ps1": "a50ff759df00aa45332aede4ebee55a1778d3ba4d48ed384c8ae08c68e5cd94e",
|
|
37
37
|
".agents/skills/setup-signoz-mcp/scripts/install_signoz_mcp.py": "ae54d74787ee80b55a1aaa84cf621857698ecf990dbf6f2b1a41379a9215abf7",
|
|
38
|
-
"AGENTS.md": "
|
|
39
|
-
"README.md": "
|
|
38
|
+
"AGENTS.md": "b2cee2d651fd2819a24d8c23d9a3082362f8295c26e71562db0a0f47bf22c550",
|
|
39
|
+
"README.md": "9c5544db33389f1d610424aa6175163bcaa6b0e08887b6d68016d8cc52e8ca1a",
|
|
40
40
|
"docs/architecture.md": "d4e8d4366c508b0feababff82cf323cdc9b38a11bdbfb721e96e0cc085d8325e",
|
|
41
41
|
"docs/diagnostic-feedback.md": "2d117bb4d748a89d057ed0d2ba72c283cdebbd3b734e5e7b57341b9d02aeaaf0",
|
|
42
42
|
"docs/diagnostic-report.md": "5ec89d3e5fdec81dfec02671875a19058e22ebd34d6d4fb9d5ab240fc459b691",
|
|
43
|
-
"docs/index.md": "
|
|
44
|
-
"docs/npm-cli.md": "
|
|
45
|
-
"docs/roadmap.md": "
|
|
43
|
+
"docs/index.md": "7f8ef0a238cb7bc817f57ceb67eca4f13baa95360fb1be355017c5aaa594122f",
|
|
44
|
+
"docs/npm-cli.md": "5e6e8db38d68b092065c5cff8f9bb13eed685dbb0f69c8f742c01596ebccb1a6",
|
|
45
|
+
"docs/roadmap.md": "ceea629a6cd037ebb3d12c46830994b4cb832f06c459b9a855832186fc373d32",
|
|
46
46
|
"docs/scenario-guide.md": "db3174d7d3cb07f4a9302c59f0f238e528cdde7fa96f5ead466b7f8aa3f4418a",
|
|
47
47
|
"docs/skill-writing-guide.md": "600e1b20e55423fe44a1f66db82fa0b3175e7e5d618590d75678846fc7180e9d",
|
|
48
48
|
"knowledge/common/knowledge-applicability.md": "f80d8c31a94b9b92f4bc98d6c5042033d63d795c6af4c90ea72d151588a536ae",
|