openclaw-telegram-manager 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/commands/archive.d.ts +4 -0
- package/dist/commands/archive.d.ts.map +1 -0
- package/dist/commands/archive.js +71 -0
- package/dist/commands/archive.js.map +1 -0
- package/dist/commands/doctor-all.d.ts +3 -0
- package/dist/commands/doctor-all.d.ts.map +1 -0
- package/dist/commands/doctor-all.js +193 -0
- package/dist/commands/doctor-all.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +74 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/help.d.ts +4 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +8 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +304 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +22 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/rename.d.ts +3 -0
- package/dist/commands/rename.d.ts.map +1 -0
- package/dist/commands/rename.js +115 -0
- package/dist/commands/rename.js.map +1 -0
- package/dist/commands/snooze.d.ts +3 -0
- package/dist/commands/snooze.d.ts.map +1 -0
- package/dist/commands/snooze.js +52 -0
- package/dist/commands/snooze.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +48 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +38 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/upgrade.d.ts +3 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +52 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/audit.d.ts +12 -0
- package/dist/lib/audit.d.ts.map +1 -0
- package/dist/lib/audit.js +35 -0
- package/dist/lib/audit.js.map +1 -0
- package/dist/lib/auth.d.ts +26 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +73 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/capsule.d.ts +27 -0
- package/dist/lib/capsule.d.ts.map +1 -0
- package/dist/lib/capsule.js +130 -0
- package/dist/lib/capsule.js.map +1 -0
- package/dist/lib/config-restart.d.ts +23 -0
- package/dist/lib/config-restart.d.ts.map +1 -0
- package/dist/lib/config-restart.js +129 -0
- package/dist/lib/config-restart.js.map +1 -0
- package/dist/lib/doctor-checks.d.ts +50 -0
- package/dist/lib/doctor-checks.d.ts.map +1 -0
- package/dist/lib/doctor-checks.js +421 -0
- package/dist/lib/doctor-checks.js.map +1 -0
- package/dist/lib/include-generator.d.ts +35 -0
- package/dist/lib/include-generator.d.ts.map +1 -0
- package/dist/lib/include-generator.js +140 -0
- package/dist/lib/include-generator.js.map +1 -0
- package/dist/lib/registry.d.ts +27 -0
- package/dist/lib/registry.d.ts.map +1 -0
- package/dist/lib/registry.js +154 -0
- package/dist/lib/registry.js.map +1 -0
- package/dist/lib/security.d.ts +57 -0
- package/dist/lib/security.d.ts.map +1 -0
- package/dist/lib/security.js +133 -0
- package/dist/lib/security.js.map +1 -0
- package/dist/lib/telegram.d.ts +55 -0
- package/dist/lib/telegram.d.ts.map +1 -0
- package/dist/lib/telegram.js +254 -0
- package/dist/lib/telegram.js.map +1 -0
- package/dist/lib/types.d.ts +120 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +85 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/setup.d.ts +3 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +333 -0
- package/dist/setup.js.map +1 -0
- package/dist/tool.d.ts +15 -0
- package/dist/tool.d.ts.map +1 -0
- package/dist/tool.js +201 -0
- package/dist/tool.js.map +1 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +48 -0
- package/skills/topic/SKILL.md +35 -0
- package/src/commands/archive.ts +89 -0
- package/src/commands/doctor-all.ts +243 -0
- package/src/commands/doctor.ts +100 -0
- package/src/commands/help.ts +11 -0
- package/src/commands/init.ts +376 -0
- package/src/commands/list.ts +28 -0
- package/src/commands/rename.ts +140 -0
- package/src/commands/snooze.ts +69 -0
- package/src/commands/status.ts +59 -0
- package/src/commands/sync.ts +46 -0
- package/src/commands/upgrade.ts +64 -0
- package/src/index.ts +54 -0
- package/src/lib/audit.ts +44 -0
- package/src/lib/auth.ts +96 -0
- package/src/lib/capsule.ts +206 -0
- package/src/lib/config-restart.ts +167 -0
- package/src/lib/doctor-checks.ts +639 -0
- package/src/lib/include-generator.ts +174 -0
- package/src/lib/registry.ts +197 -0
- package/src/lib/security.ts +174 -0
- package/src/lib/telegram.ts +311 -0
- package/src/lib/types.ts +172 -0
- package/src/setup.ts +402 -0
- package/src/templates/base/COMMANDS.md +3 -0
- package/src/templates/base/CRON.md +3 -0
- package/src/templates/base/LINKS.md +3 -0
- package/src/templates/base/NOTES.md +3 -0
- package/src/templates/base/README.md +3 -0
- package/src/templates/base/STATUS.md +13 -0
- package/src/templates/base/TODO.md +11 -0
- package/src/templates/overlays/coding/ARCHITECTURE.md +3 -0
- package/src/templates/overlays/coding/DEPLOY.md +3 -0
- package/src/templates/overlays/marketing/CAMPAIGNS.md +3 -0
- package/src/templates/overlays/marketing/METRICS.md +3 -0
- package/src/templates/overlays/research/FINDINGS.md +3 -0
- package/src/templates/overlays/research/SOURCES.md +3 -0
- package/src/tool.ts +282 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// ── Config restart via config.patch no-op ──────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Triggers Gateway restart by patching a no-op value, which causes
|
|
4
|
+
// OpenClaw to reload all $include files.
|
|
5
|
+
// ── Cooldown tracking (module-level) ───────────────────────────────────
|
|
6
|
+
const COOLDOWN_MS = 60_000; // 60 seconds
|
|
7
|
+
let lastRestartTimestamp = 0;
|
|
8
|
+
// ── Backoff config ─────────────────────────────────────────────────────
|
|
9
|
+
const MAX_RETRIES = 3;
|
|
10
|
+
const BASE_DELAY_MS = 1000; // 1s, 2s, 4s exponential backoff
|
|
11
|
+
// ── Main function ──────────────────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Trigger a Gateway restart via config.patch no-op.
|
|
14
|
+
*
|
|
15
|
+
* - Enforces a 60-second cooldown between calls
|
|
16
|
+
* - Retries up to 3 times on baseHash mismatch with exponential backoff
|
|
17
|
+
* - Falls back to a user message if RPC is unavailable
|
|
18
|
+
*/
|
|
19
|
+
export async function triggerRestart(rpc, logger) {
|
|
20
|
+
// Cooldown check
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
if (now - lastRestartTimestamp < COOLDOWN_MS) {
|
|
23
|
+
const remainingSec = Math.ceil((COOLDOWN_MS - (now - lastRestartTimestamp)) / 1000);
|
|
24
|
+
logger.info(`Config restart cooldown active. ${remainingSec}s remaining.`);
|
|
25
|
+
return {
|
|
26
|
+
success: true, // Not a failure — just throttled
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// If RPC is not available, return fallback
|
|
30
|
+
if (!rpc) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
fallbackMessage: 'Config updated. Run `openclaw gateway restart` or send SIGUSR1 to apply.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Retry loop with exponential backoff
|
|
37
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
38
|
+
try {
|
|
39
|
+
// Get current config to capture baseHash
|
|
40
|
+
const configResult = await rpc.call('config.get', {});
|
|
41
|
+
const baseHash = configResult['baseHash'];
|
|
42
|
+
if (!baseHash) {
|
|
43
|
+
logger.warn('config.get did not return baseHash; attempting patch without it');
|
|
44
|
+
}
|
|
45
|
+
// Patch with no-op change to trigger restart
|
|
46
|
+
const patchParams = {
|
|
47
|
+
patch: {
|
|
48
|
+
skills: {
|
|
49
|
+
entries: {
|
|
50
|
+
'telegram-manager': {
|
|
51
|
+
lastSync: new Date().toISOString(),
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
if (baseHash) {
|
|
58
|
+
patchParams['baseHash'] = baseHash;
|
|
59
|
+
}
|
|
60
|
+
await rpc.call('config.patch', patchParams);
|
|
61
|
+
// Success — update cooldown timestamp
|
|
62
|
+
lastRestartTimestamp = Date.now();
|
|
63
|
+
logger.info('Gateway restart triggered via config.patch');
|
|
64
|
+
return { success: true };
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const isBaseHashMismatch = isHashMismatchError(err);
|
|
68
|
+
if (isBaseHashMismatch && attempt < MAX_RETRIES) {
|
|
69
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
70
|
+
logger.warn(`config.patch baseHash mismatch (attempt ${attempt + 1}/${MAX_RETRIES + 1}). Retrying in ${delay}ms...`);
|
|
71
|
+
await sleep(delay);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
// All retries exhausted or non-retryable error
|
|
75
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
76
|
+
logger.error(`config.patch failed after ${attempt + 1} attempt(s): ${errMsg}`);
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
fallbackMessage: 'Config updated. Run `openclaw gateway restart` or send SIGUSR1 to apply.',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Should not reach here, but just in case
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
fallbackMessage: 'Config updated. Run `openclaw gateway restart` or send SIGUSR1 to apply.',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
90
|
+
function isHashMismatchError(err) {
|
|
91
|
+
if (err && typeof err === 'object') {
|
|
92
|
+
// Check for common error shape
|
|
93
|
+
if ('code' in err && err.code === 'BASE_HASH_MISMATCH') {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if ('message' in err) {
|
|
97
|
+
const msg = err.message;
|
|
98
|
+
return msg.toLowerCase().includes('basehash') || msg.toLowerCase().includes('hash mismatch');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
function sleep(ms) {
|
|
104
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if configWrites is enabled in the plugin config via RPC.
|
|
108
|
+
*/
|
|
109
|
+
export async function getConfigWrites(rpc) {
|
|
110
|
+
if (!rpc)
|
|
111
|
+
return false;
|
|
112
|
+
try {
|
|
113
|
+
const config = await rpc.call('config.get', {});
|
|
114
|
+
const skills = config['skills'];
|
|
115
|
+
const entries = skills?.['entries'];
|
|
116
|
+
const tmConfig = entries?.['telegram-manager'];
|
|
117
|
+
return tmConfig?.['configWrites'] === true;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Reset the cooldown timer (for testing purposes).
|
|
125
|
+
*/
|
|
126
|
+
export function resetCooldown() {
|
|
127
|
+
lastRestartTimestamp = 0;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=config-restart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-restart.js","sourceRoot":"","sources":["../../src/lib/config-restart.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { TopicEntry, DoctorCheckResult, Registry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check that the registry entry's capsule path exists on disk.
|
|
4
|
+
*/
|
|
5
|
+
export declare function runRegistryChecks(entry: TopicEntry, projectsBase: string): DoctorCheckResult[];
|
|
6
|
+
/**
|
|
7
|
+
* Check for capsule folders in projects/ that have no matching registry entry.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runOrphanCheck(projectsBase: string, registrySlugs: Set<string>): DoctorCheckResult[];
|
|
10
|
+
/**
|
|
11
|
+
* Check capsule structure: required files, overlay files, capsule version.
|
|
12
|
+
*/
|
|
13
|
+
export declare function runCapsuleChecks(entry: TopicEntry, projectsBase: string): DoctorCheckResult[];
|
|
14
|
+
/**
|
|
15
|
+
* Check STATUS.md content quality.
|
|
16
|
+
*/
|
|
17
|
+
export declare function runStatusQualityChecks(statusContent: string, entry: TopicEntry): DoctorCheckResult[];
|
|
18
|
+
/**
|
|
19
|
+
* Check that task IDs in "Next 3 actions" exist in TODO.md.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runNextVsTodoChecks(statusContent: string, todoContent: string): DoctorCheckResult[];
|
|
22
|
+
/**
|
|
23
|
+
* Check COMMANDS.md and LINKS.md for relevant topic types.
|
|
24
|
+
*/
|
|
25
|
+
export declare function runCommandsLinksChecks(entry: TopicEntry, capsuleFiles: Map<string, string>): DoctorCheckResult[];
|
|
26
|
+
/**
|
|
27
|
+
* Check CRON.md for job ID presence and optionally validate against cron/jobs.json.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runCronChecks(cronContent: string, cronJobsPath?: string): DoctorCheckResult[];
|
|
30
|
+
/**
|
|
31
|
+
* Check per-topic systemPrompt and skills against canonical templates.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runConfigChecks(entry: TopicEntry, includeContent: string, registry: Registry): DoctorCheckResult[];
|
|
34
|
+
/**
|
|
35
|
+
* Check if the generated include file's registry-hash matches the current registry.
|
|
36
|
+
*/
|
|
37
|
+
export declare function runIncludeDriftCheck(includeFileContent: string, registry: Registry): DoctorCheckResult[];
|
|
38
|
+
/**
|
|
39
|
+
* Check for spam control: if consecutiveSilentDoctors >= 3, suggest auto-snooze.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runSpamControlCheck(entry: TopicEntry): DoctorCheckResult[];
|
|
42
|
+
/**
|
|
43
|
+
* Run all applicable doctor checks for a single topic entry.
|
|
44
|
+
* Returns combined results from all check categories.
|
|
45
|
+
*
|
|
46
|
+
* This is a convenience function for command handlers.
|
|
47
|
+
* It reads capsule files and runs all checks.
|
|
48
|
+
*/
|
|
49
|
+
export declare function runAllChecksForTopic(entry: TopicEntry, projectsBase: string, includeContent?: string, registry?: Registry, cronJobsPath?: string): DoctorCheckResult[];
|
|
50
|
+
//# sourceMappingURL=doctor-checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-checks.d.ts","sourceRoot":"","sources":["../../src/lib/doctor-checks.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAqB1E;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,MAAM,GACnB,iBAAiB,EAAE,CA2BrB;AAID;;GAEG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB,iBAAiB,EAAE,CA4BrB;AAID;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,MAAM,GACnB,iBAAiB,EAAE,CAkDrB;AAUD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,UAAU,GAChB,iBAAiB,EAAE,CAiFrB;AAID;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,iBAAiB,EAAE,CAoCrB;AAID;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,iBAAiB,EAAE,CA4BrB;AAmBD;;GAEG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,iBAAiB,EAAE,CA4CrB;AAID;;GAEG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,QAAQ,GACjB,iBAAiB,EAAE,CA2DrB;AAID;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,kBAAkB,EAAE,MAAM,EAC1B,QAAQ,EAAE,QAAQ,GACjB,iBAAiB,EAAE,CA8BrB;AAID;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,GAChB,iBAAiB,EAAE,CAerB;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,UAAU,EACjB,YAAY,EAAE,MAAM,EACpB,cAAc,CAAC,EAAE,MAAM,EACvB,QAAQ,CAAC,EAAE,QAAQ,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,iBAAiB,EAAE,CAmDrB"}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { Severity, CAPSULE_VERSION, OVERLAY_FILES, SPAM_THRESHOLD, } from './types.js';
|
|
4
|
+
import { jailCheck } from './security.js';
|
|
5
|
+
import { computeRegistryHash, extractRegistryHash } from './include-generator.js';
|
|
6
|
+
// ── Helper: check if a checkId should be ignored ───────────────────────
|
|
7
|
+
function isIgnored(entry, checkId) {
|
|
8
|
+
return entry.ignoreChecks.includes(checkId);
|
|
9
|
+
}
|
|
10
|
+
function check(severity, checkId, message, fixable) {
|
|
11
|
+
return { severity, checkId, message, fixable };
|
|
12
|
+
}
|
|
13
|
+
// ── Registry / mapping checks ──────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Check that the registry entry's capsule path exists on disk.
|
|
16
|
+
*/
|
|
17
|
+
export function runRegistryChecks(entry, projectsBase) {
|
|
18
|
+
const results = [];
|
|
19
|
+
const capsuleDir = path.join(projectsBase, entry.slug);
|
|
20
|
+
// Check path exists
|
|
21
|
+
if (!fs.existsSync(capsuleDir)) {
|
|
22
|
+
results.push(check(Severity.ERROR, 'pathMissing', `Capsule path does not exist: projects/${entry.slug}/`, false));
|
|
23
|
+
return results; // No point checking further if path doesn't exist
|
|
24
|
+
}
|
|
25
|
+
// Check that the folder name matches the slug
|
|
26
|
+
try {
|
|
27
|
+
const stat = fs.statSync(capsuleDir);
|
|
28
|
+
if (!stat.isDirectory()) {
|
|
29
|
+
results.push(check(Severity.ERROR, 'pathNotDir', `projects/${entry.slug} exists but is not a directory`, false));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
results.push(check(Severity.ERROR, 'pathStatFailed', `Cannot stat projects/${entry.slug}/`, false));
|
|
34
|
+
}
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
// ── Orphan detection ───────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Check for capsule folders in projects/ that have no matching registry entry.
|
|
40
|
+
*/
|
|
41
|
+
export function runOrphanCheck(projectsBase, registrySlugs) {
|
|
42
|
+
const results = [];
|
|
43
|
+
let entries;
|
|
44
|
+
try {
|
|
45
|
+
entries = fs.readdirSync(projectsBase, { withFileTypes: true });
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return results; // Can't read directory — skip
|
|
49
|
+
}
|
|
50
|
+
for (const dirent of entries) {
|
|
51
|
+
if (!dirent.isDirectory())
|
|
52
|
+
continue;
|
|
53
|
+
// Skip hidden dirs and special files
|
|
54
|
+
if (dirent.name.startsWith('.') || dirent.name === 'audit.jsonl')
|
|
55
|
+
continue;
|
|
56
|
+
if (!registrySlugs.has(dirent.name)) {
|
|
57
|
+
results.push(check(Severity.WARN, 'orphanFolder', `Folder projects/${dirent.name}/ has no registry entry. Register with /topic init or delete.`, false));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return results;
|
|
61
|
+
}
|
|
62
|
+
// ── Capsule structure checks ───────────────────────────────────────────
|
|
63
|
+
/**
|
|
64
|
+
* Check capsule structure: required files, overlay files, capsule version.
|
|
65
|
+
*/
|
|
66
|
+
export function runCapsuleChecks(entry, projectsBase) {
|
|
67
|
+
const results = [];
|
|
68
|
+
const capsuleDir = path.join(projectsBase, entry.slug);
|
|
69
|
+
if (!fs.existsSync(capsuleDir))
|
|
70
|
+
return results;
|
|
71
|
+
// STATUS.md is critical
|
|
72
|
+
if (!fs.existsSync(path.join(capsuleDir, 'STATUS.md'))) {
|
|
73
|
+
results.push(check(Severity.ERROR, 'statusMissing', 'STATUS.md is missing from capsule', true));
|
|
74
|
+
}
|
|
75
|
+
// TODO.md is important
|
|
76
|
+
if (!fs.existsSync(path.join(capsuleDir, 'TODO.md'))) {
|
|
77
|
+
if (!isIgnored(entry, 'todoMissing')) {
|
|
78
|
+
results.push(check(Severity.WARN, 'todoMissing', 'TODO.md is missing from capsule', true));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Overlay files are optional but worth noting
|
|
82
|
+
const overlays = OVERLAY_FILES[entry.type] ?? [];
|
|
83
|
+
for (const file of overlays) {
|
|
84
|
+
if (!fs.existsSync(path.join(capsuleDir, file))) {
|
|
85
|
+
const checkId = `overlayMissing:${file}`;
|
|
86
|
+
if (!isIgnored(entry, checkId)) {
|
|
87
|
+
results.push(check(Severity.INFO, checkId, `Optional overlay ${file} missing for type "${entry.type}"`, true));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Capsule version behind
|
|
92
|
+
if (entry.capsuleVersion < CAPSULE_VERSION) {
|
|
93
|
+
if (!isIgnored(entry, 'capsuleVersionBehind')) {
|
|
94
|
+
results.push(check(Severity.INFO, 'capsuleVersionBehind', `Capsule version ${entry.capsuleVersion} is behind current ${CAPSULE_VERSION}. Run /topic upgrade.`, false));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return results;
|
|
98
|
+
}
|
|
99
|
+
// ── STATUS.md quality checks ───────────────────────────────────────────
|
|
100
|
+
const LAST_DONE_RE = /^##\s*Last done\s*\(UTC\)/im;
|
|
101
|
+
const NEXT_ACTIONS_RE = /^##\s*Next 3 actions/im;
|
|
102
|
+
const TIMESTAMP_RE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
103
|
+
const TASK_ID_RE = /\[T-\d+\]/g;
|
|
104
|
+
const ADHOC_RE = /\[AD-HOC\]/g;
|
|
105
|
+
/**
|
|
106
|
+
* Check STATUS.md content quality.
|
|
107
|
+
*/
|
|
108
|
+
export function runStatusQualityChecks(statusContent, entry) {
|
|
109
|
+
const results = [];
|
|
110
|
+
// "Last done (UTC)" section check
|
|
111
|
+
if (!LAST_DONE_RE.test(statusContent)) {
|
|
112
|
+
if (!isIgnored(entry, 'lastDoneMissing')) {
|
|
113
|
+
results.push(check(Severity.ERROR, 'lastDoneMissing', 'STATUS.md missing "Last done (UTC)" section', true));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// Check for timestamp in the section
|
|
118
|
+
const lastDoneIndex = statusContent.search(LAST_DONE_RE);
|
|
119
|
+
const sectionAfter = statusContent.slice(lastDoneIndex);
|
|
120
|
+
const nextSectionIndex = sectionAfter.indexOf('\n## ', 1);
|
|
121
|
+
const lastDoneSection = nextSectionIndex > 0
|
|
122
|
+
? sectionAfter.slice(0, nextSectionIndex)
|
|
123
|
+
: sectionAfter;
|
|
124
|
+
if (!TIMESTAMP_RE.test(lastDoneSection)) {
|
|
125
|
+
if (!isIgnored(entry, 'lastDoneNoTimestamp')) {
|
|
126
|
+
results.push(check(Severity.ERROR, 'lastDoneNoTimestamp', 'STATUS.md "Last done" section has no timestamp', true));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else if (entry.status === 'active') {
|
|
130
|
+
// Check timestamp age (default: 3 days)
|
|
131
|
+
const tsMatch = lastDoneSection.match(TIMESTAMP_RE);
|
|
132
|
+
if (tsMatch) {
|
|
133
|
+
const ts = new Date(tsMatch[0]);
|
|
134
|
+
const ageDays = (Date.now() - ts.getTime()) / (1000 * 60 * 60 * 24);
|
|
135
|
+
if (ageDays > 3) {
|
|
136
|
+
if (!isIgnored(entry, 'lastDoneStale')) {
|
|
137
|
+
results.push(check(Severity.WARN, 'lastDoneStale', `STATUS.md "Last done" timestamp is ${Math.floor(ageDays)} days old`, false));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// "Next 3 actions" section check
|
|
144
|
+
if (!NEXT_ACTIONS_RE.test(statusContent)) {
|
|
145
|
+
if (!isIgnored(entry, 'nextActionsMissing')) {
|
|
146
|
+
results.push(check(Severity.ERROR, 'nextActionsMissing', 'STATUS.md missing "Next 3 actions" section', true));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Check that next actions contain task IDs
|
|
151
|
+
const nextActionsIndex = statusContent.search(NEXT_ACTIONS_RE);
|
|
152
|
+
const sectionAfter = statusContent.slice(nextActionsIndex);
|
|
153
|
+
const nextSectionIndex = sectionAfter.indexOf('\n## ', 1);
|
|
154
|
+
const nextActionsSection = nextSectionIndex > 0
|
|
155
|
+
? sectionAfter.slice(0, nextSectionIndex)
|
|
156
|
+
: sectionAfter;
|
|
157
|
+
const taskIds = nextActionsSection.match(TASK_ID_RE) ?? [];
|
|
158
|
+
const adhocs = nextActionsSection.match(ADHOC_RE) ?? [];
|
|
159
|
+
if (taskIds.length === 0 && adhocs.length === 0) {
|
|
160
|
+
if (!isIgnored(entry, 'nextActionsEmpty')) {
|
|
161
|
+
results.push(check(Severity.WARN, 'nextActionsEmpty', '"Next 3 actions" has no task IDs or entries', false));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return results;
|
|
166
|
+
}
|
|
167
|
+
// ── Next vs TODO cross-reference ───────────────────────────────────────
|
|
168
|
+
/**
|
|
169
|
+
* Check that task IDs in "Next 3 actions" exist in TODO.md.
|
|
170
|
+
*/
|
|
171
|
+
export function runNextVsTodoChecks(statusContent, todoContent) {
|
|
172
|
+
const results = [];
|
|
173
|
+
// Extract next actions section
|
|
174
|
+
const nextActionsIndex = statusContent.search(NEXT_ACTIONS_RE);
|
|
175
|
+
if (nextActionsIndex < 0)
|
|
176
|
+
return results;
|
|
177
|
+
const sectionAfter = statusContent.slice(nextActionsIndex);
|
|
178
|
+
const nextSectionIndex = sectionAfter.indexOf('\n## ', 1);
|
|
179
|
+
const nextActionsSection = nextSectionIndex > 0
|
|
180
|
+
? sectionAfter.slice(0, nextSectionIndex)
|
|
181
|
+
: sectionAfter;
|
|
182
|
+
// Get task IDs from next actions
|
|
183
|
+
const nextTaskIds = nextActionsSection.match(TASK_ID_RE) ?? [];
|
|
184
|
+
if (nextTaskIds.length === 0)
|
|
185
|
+
return results;
|
|
186
|
+
// Get task IDs from TODO
|
|
187
|
+
const todoTaskIds = new Set(todoContent.match(TASK_ID_RE) ?? []);
|
|
188
|
+
// Find task IDs in next that are not in TODO
|
|
189
|
+
const missing = nextTaskIds.filter((id) => !todoTaskIds.has(id));
|
|
190
|
+
// Only warn if 2+ are missing (allows 1 stale reference)
|
|
191
|
+
if (missing.length >= 2) {
|
|
192
|
+
results.push(check(Severity.WARN, 'nextNotInTodo', `${missing.length} task IDs in "Next 3 actions" not found in TODO.md: ${missing.join(', ')}`, false));
|
|
193
|
+
}
|
|
194
|
+
return results;
|
|
195
|
+
}
|
|
196
|
+
// ── Commands / Links checks ────────────────────────────────────────────
|
|
197
|
+
/**
|
|
198
|
+
* Check COMMANDS.md and LINKS.md for relevant topic types.
|
|
199
|
+
*/
|
|
200
|
+
export function runCommandsLinksChecks(entry, capsuleFiles) {
|
|
201
|
+
const results = [];
|
|
202
|
+
// COMMANDS.md empty for coding topics
|
|
203
|
+
if (entry.type === 'coding') {
|
|
204
|
+
const commandsContent = capsuleFiles.get('COMMANDS.md');
|
|
205
|
+
if (commandsContent !== undefined && isEffectivelyEmpty(commandsContent)) {
|
|
206
|
+
if (!isIgnored(entry, 'commandsEmpty')) {
|
|
207
|
+
results.push(check(Severity.INFO, 'commandsEmpty', 'COMMANDS.md is empty for a coding topic', false));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// LINKS.md empty for coding or research
|
|
212
|
+
if (entry.type === 'coding' || entry.type === 'research') {
|
|
213
|
+
const linksContent = capsuleFiles.get('LINKS.md');
|
|
214
|
+
if (linksContent !== undefined && isEffectivelyEmpty(linksContent)) {
|
|
215
|
+
if (!isIgnored(entry, 'linksEmpty')) {
|
|
216
|
+
results.push(check(Severity.INFO, 'linksEmpty', 'LINKS.md is empty for a coding/research topic', false));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return results;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check if a markdown file is effectively empty (only has a heading and template text).
|
|
224
|
+
*/
|
|
225
|
+
function isEffectivelyEmpty(content) {
|
|
226
|
+
// Remove markdown heading lines and template placeholders
|
|
227
|
+
const stripped = content
|
|
228
|
+
.replace(/^#.*$/gm, '') // headings
|
|
229
|
+
.replace(/^_.*_$/gm, '') // italic template text
|
|
230
|
+
.replace(/\s+/g, '') // whitespace
|
|
231
|
+
.trim();
|
|
232
|
+
return stripped.length === 0;
|
|
233
|
+
}
|
|
234
|
+
// ── Cron checks ────────────────────────────────────────────────────────
|
|
235
|
+
const JOB_ID_RE = /[a-zA-Z0-9_-]{8,}/;
|
|
236
|
+
/**
|
|
237
|
+
* Check CRON.md for job ID presence and optionally validate against cron/jobs.json.
|
|
238
|
+
*/
|
|
239
|
+
export function runCronChecks(cronContent, cronJobsPath) {
|
|
240
|
+
const results = [];
|
|
241
|
+
// Skip if cron content is effectively empty
|
|
242
|
+
if (isEffectivelyEmpty(cronContent))
|
|
243
|
+
return results;
|
|
244
|
+
// Check for job IDs in CRON.md
|
|
245
|
+
const lines = cronContent.split('\n').filter((l) => !l.startsWith('#') && l.trim().length > 0);
|
|
246
|
+
const hasJobIds = lines.some((line) => JOB_ID_RE.test(line));
|
|
247
|
+
if (!hasJobIds) {
|
|
248
|
+
results.push(check(Severity.WARN, 'cronNoJobIds', 'CRON.md lists jobs but has no recognizable job IDs', false));
|
|
249
|
+
return results;
|
|
250
|
+
}
|
|
251
|
+
// Optionally validate job IDs against cron/jobs.json
|
|
252
|
+
if (cronJobsPath && fs.existsSync(cronJobsPath)) {
|
|
253
|
+
try {
|
|
254
|
+
const jobsRaw = fs.readFileSync(cronJobsPath, 'utf-8');
|
|
255
|
+
const jobs = JSON.parse(jobsRaw);
|
|
256
|
+
const knownJobIds = new Set(Object.keys(jobs));
|
|
257
|
+
// Extract job IDs from CRON.md lines
|
|
258
|
+
for (const line of lines) {
|
|
259
|
+
const match = line.match(JOB_ID_RE);
|
|
260
|
+
if (match && !knownJobIds.has(match[0])) {
|
|
261
|
+
results.push(check(Severity.WARN, 'cronJobNotFound', `Job ID "${match[0]}" from CRON.md not found in cron/jobs.json`, false));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
// Can't read jobs file — skip validation
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return results;
|
|
270
|
+
}
|
|
271
|
+
// ── Config enforcement checks ──────────────────────────────────────────
|
|
272
|
+
/**
|
|
273
|
+
* Check per-topic systemPrompt and skills against canonical templates.
|
|
274
|
+
*/
|
|
275
|
+
export function runConfigChecks(entry, includeContent, registry) {
|
|
276
|
+
const results = [];
|
|
277
|
+
// Parse the include to check this topic's config
|
|
278
|
+
let includeObj;
|
|
279
|
+
try {
|
|
280
|
+
// Strip comment lines before parsing
|
|
281
|
+
const stripped = includeContent
|
|
282
|
+
.split('\n')
|
|
283
|
+
.filter((l) => !l.startsWith('//'))
|
|
284
|
+
.join('\n');
|
|
285
|
+
includeObj = JSON.parse(stripped);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// Try json5 parsing - but we don't import json5 here to keep this pure
|
|
289
|
+
// If we can't parse, skip
|
|
290
|
+
return results;
|
|
291
|
+
}
|
|
292
|
+
const groupConfig = includeObj[entry.groupId];
|
|
293
|
+
if (!groupConfig) {
|
|
294
|
+
if (!isIgnored(entry, 'configGroupMissing')) {
|
|
295
|
+
results.push(check(Severity.WARN, 'configGroupMissing', `Group ${entry.groupId} missing from generated include`, false));
|
|
296
|
+
}
|
|
297
|
+
return results;
|
|
298
|
+
}
|
|
299
|
+
const topics = groupConfig['topics'];
|
|
300
|
+
const topicConfig = topics?.[entry.threadId];
|
|
301
|
+
if (!topicConfig) {
|
|
302
|
+
if (!isIgnored(entry, 'configTopicMissing')) {
|
|
303
|
+
results.push(check(Severity.WARN, 'configTopicMissing', `Topic config missing for thread ${entry.threadId}`, false));
|
|
304
|
+
}
|
|
305
|
+
return results;
|
|
306
|
+
}
|
|
307
|
+
// Check systemPrompt exists
|
|
308
|
+
if (!topicConfig['systemPrompt']) {
|
|
309
|
+
if (!isIgnored(entry, 'configNoSystemPrompt')) {
|
|
310
|
+
results.push(check(Severity.WARN, 'configNoSystemPrompt', 'Per-topic systemPrompt is missing in generated include', false));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Check skills exist
|
|
314
|
+
if (!topicConfig['skills'] || !Array.isArray(topicConfig['skills'])) {
|
|
315
|
+
if (!isIgnored(entry, 'configNoSkills')) {
|
|
316
|
+
results.push(check(Severity.WARN, 'configNoSkills', 'Per-topic skills list is missing in generated include', false));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return results;
|
|
320
|
+
}
|
|
321
|
+
// ── Include drift detection ────────────────────────────────────────────
|
|
322
|
+
/**
|
|
323
|
+
* Check if the generated include file's registry-hash matches the current registry.
|
|
324
|
+
*/
|
|
325
|
+
export function runIncludeDriftCheck(includeFileContent, registry) {
|
|
326
|
+
const results = [];
|
|
327
|
+
const fileHash = extractRegistryHash(includeFileContent);
|
|
328
|
+
if (!fileHash) {
|
|
329
|
+
results.push(check(Severity.WARN, 'includeDrift', 'Generated include file has no registry-hash comment. Run /topic sync.', false));
|
|
330
|
+
return results;
|
|
331
|
+
}
|
|
332
|
+
const currentHash = computeRegistryHash(registry.topics);
|
|
333
|
+
if (fileHash !== currentHash) {
|
|
334
|
+
results.push(check(Severity.WARN, 'includeDrift', 'Generated include is out of sync with registry. Run /topic sync.', false));
|
|
335
|
+
}
|
|
336
|
+
return results;
|
|
337
|
+
}
|
|
338
|
+
// ── Spam control check ─────────────────────────────────────────────────
|
|
339
|
+
/**
|
|
340
|
+
* Check for spam control: if consecutiveSilentDoctors >= 3, suggest auto-snooze.
|
|
341
|
+
*/
|
|
342
|
+
export function runSpamControlCheck(entry) {
|
|
343
|
+
const results = [];
|
|
344
|
+
if (entry.consecutiveSilentDoctors >= SPAM_THRESHOLD) {
|
|
345
|
+
results.push(check(Severity.INFO, 'spamControl', `${entry.consecutiveSilentDoctors} consecutive doctor reports with no user interaction. Auto-snoozing for 30 days.`, true));
|
|
346
|
+
}
|
|
347
|
+
return results;
|
|
348
|
+
}
|
|
349
|
+
// ── Convenience: run all checks for a single topic ─────────────────────
|
|
350
|
+
/**
|
|
351
|
+
* Run all applicable doctor checks for a single topic entry.
|
|
352
|
+
* Returns combined results from all check categories.
|
|
353
|
+
*
|
|
354
|
+
* This is a convenience function for command handlers.
|
|
355
|
+
* It reads capsule files and runs all checks.
|
|
356
|
+
*/
|
|
357
|
+
export function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cronJobsPath) {
|
|
358
|
+
const results = [];
|
|
359
|
+
const capsuleDir = path.join(projectsBase, entry.slug);
|
|
360
|
+
// Registry checks
|
|
361
|
+
results.push(...runRegistryChecks(entry, projectsBase));
|
|
362
|
+
// If path doesn't exist, skip capsule-dependent checks
|
|
363
|
+
if (!fs.existsSync(capsuleDir))
|
|
364
|
+
return results;
|
|
365
|
+
// Capsule structure checks
|
|
366
|
+
results.push(...runCapsuleChecks(entry, projectsBase));
|
|
367
|
+
// Read capsule files for content-based checks
|
|
368
|
+
const capsuleFiles = readCapsuleFiles(capsuleDir);
|
|
369
|
+
// STATUS quality checks
|
|
370
|
+
const statusContent = capsuleFiles.get('STATUS.md');
|
|
371
|
+
if (statusContent) {
|
|
372
|
+
results.push(...runStatusQualityChecks(statusContent, entry));
|
|
373
|
+
// Next vs TODO checks
|
|
374
|
+
const todoContent = capsuleFiles.get('TODO.md');
|
|
375
|
+
if (todoContent && !isIgnored(entry, 'nextNotInTodo')) {
|
|
376
|
+
results.push(...runNextVsTodoChecks(statusContent, todoContent));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Commands / Links checks
|
|
380
|
+
results.push(...runCommandsLinksChecks(entry, capsuleFiles));
|
|
381
|
+
// Cron checks
|
|
382
|
+
const cronContent = capsuleFiles.get('CRON.md');
|
|
383
|
+
if (cronContent) {
|
|
384
|
+
results.push(...runCronChecks(cronContent, cronJobsPath));
|
|
385
|
+
}
|
|
386
|
+
// Config checks (if include content provided)
|
|
387
|
+
if (includeContent && registry) {
|
|
388
|
+
results.push(...runConfigChecks(entry, includeContent, registry));
|
|
389
|
+
}
|
|
390
|
+
// Include drift (if include content provided)
|
|
391
|
+
if (includeContent && registry) {
|
|
392
|
+
results.push(...runIncludeDriftCheck(includeContent, registry));
|
|
393
|
+
}
|
|
394
|
+
// Spam control
|
|
395
|
+
results.push(...runSpamControlCheck(entry));
|
|
396
|
+
return results;
|
|
397
|
+
}
|
|
398
|
+
// ── File reading helper ────────────────────────────────────────────────
|
|
399
|
+
function readCapsuleFiles(capsuleDir) {
|
|
400
|
+
const files = new Map();
|
|
401
|
+
const filenames = [
|
|
402
|
+
'STATUS.md', 'TODO.md', 'COMMANDS.md', 'LINKS.md',
|
|
403
|
+
'CRON.md', 'NOTES.md', 'README.md',
|
|
404
|
+
'ARCHITECTURE.md', 'DEPLOY.md',
|
|
405
|
+
'SOURCES.md', 'FINDINGS.md',
|
|
406
|
+
'CAMPAIGNS.md', 'METRICS.md',
|
|
407
|
+
];
|
|
408
|
+
for (const name of filenames) {
|
|
409
|
+
const filePath = path.join(capsuleDir, name);
|
|
410
|
+
try {
|
|
411
|
+
if (fs.existsSync(filePath)) {
|
|
412
|
+
files.set(name, fs.readFileSync(filePath, 'utf-8'));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// Skip unreadable files
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return files;
|
|
420
|
+
}
|
|
421
|
+
//# sourceMappingURL=doctor-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor-checks.js","sourceRoot":"","sources":["../../src/lib/doctor-checks.ts"],"names":[],"mappings":"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"}
|