openclaw-telegram-manager 2.7.0 → 2.9.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/README.md +8 -13
- package/dist/commands/daily-report.d.ts +1 -1
- package/dist/commands/daily-report.d.ts.map +1 -1
- package/dist/commands/daily-report.js +7 -5
- package/dist/commands/daily-report.js.map +1 -1
- package/dist/commands/doctor-all.d.ts.map +1 -1
- package/dist/commands/doctor-all.js +21 -3
- package/dist/commands/doctor-all.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +1 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/status.d.ts +0 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +3 -5
- package/dist/commands/status.js.map +1 -1
- package/dist/lib/capsule.d.ts +4 -2
- package/dist/lib/capsule.d.ts.map +1 -1
- package/dist/lib/capsule.js +80 -37
- package/dist/lib/capsule.js.map +1 -1
- package/dist/lib/doctor-checks.d.ts +7 -11
- package/dist/lib/doctor-checks.d.ts.map +1 -1
- package/dist/lib/doctor-checks.js +34 -109
- package/dist/lib/doctor-checks.js.map +1 -1
- package/dist/lib/include-generator.d.ts.map +1 -1
- package/dist/lib/include-generator.js +6 -7
- package/dist/lib/include-generator.js.map +1 -1
- package/dist/lib/types.d.ts +2 -2
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +4 -9
- package/dist/lib/types.js.map +1 -1
- package/dist/plugin.js +127 -199
- package/package.json +1 -1
- package/skills/tm/SKILL.md +24 -2
package/dist/plugin.js
CHANGED
|
@@ -8816,7 +8816,7 @@ __export(value_exports2, {
|
|
|
8816
8816
|
|
|
8817
8817
|
// src/lib/types.ts
|
|
8818
8818
|
var CURRENT_REGISTRY_VERSION = 6;
|
|
8819
|
-
var CAPSULE_VERSION =
|
|
8819
|
+
var CAPSULE_VERSION = 4;
|
|
8820
8820
|
var MAX_POST_ERROR_LENGTH = 500;
|
|
8821
8821
|
var MAX_NAME_LENGTH = 100;
|
|
8822
8822
|
var DOCTOR_ALL_COOLDOWN_MS = 60 * 60 * 1e3;
|
|
@@ -8867,20 +8867,9 @@ var RegistrySchema = Type.Object({
|
|
|
8867
8867
|
maxTopics: Type.Integer({ minimum: 1 }),
|
|
8868
8868
|
topics: Type.Record(Type.String(), TopicEntrySchema)
|
|
8869
8869
|
});
|
|
8870
|
-
var OVERLAY_FILES = {
|
|
8871
|
-
coding: ["ARCHITECTURE.md", "DEPLOY.md"],
|
|
8872
|
-
research: ["SOURCES.md", "FINDINGS.md"],
|
|
8873
|
-
marketing: ["CAMPAIGNS.md", "METRICS.md"],
|
|
8874
|
-
general: []
|
|
8875
|
-
};
|
|
8876
8870
|
var BASE_FILES = [
|
|
8877
8871
|
"README.md",
|
|
8878
8872
|
"STATUS.md",
|
|
8879
|
-
"TODO.md",
|
|
8880
|
-
"COMMANDS.md",
|
|
8881
|
-
"LINKS.md",
|
|
8882
|
-
"CRON.md",
|
|
8883
|
-
"NOTES.md",
|
|
8884
8873
|
"LEARNINGS.md"
|
|
8885
8874
|
];
|
|
8886
8875
|
function topicKey(groupId, threadId) {
|
|
@@ -9147,17 +9136,65 @@ function buildAuditEntry(userId, cmd, slug, detail) {
|
|
|
9147
9136
|
// src/lib/capsule.ts
|
|
9148
9137
|
import * as fs4 from "node:fs";
|
|
9149
9138
|
import * as path4 from "node:path";
|
|
9150
|
-
var
|
|
9151
|
-
|
|
9139
|
+
var README_UNIVERSAL = (name) => `# ${name}
|
|
9140
|
+
|
|
9141
|
+
## What is this about?
|
|
9152
9142
|
|
|
9153
9143
|
_Describe what this topic is about._
|
|
9144
|
+
|
|
9145
|
+
## Goal
|
|
9146
|
+
|
|
9147
|
+
_What does success look like?_
|
|
9148
|
+
|
|
9149
|
+
## Key resources
|
|
9150
|
+
|
|
9151
|
+
_URLs, repos, paths, dashboards \u2014 anything the AI needs after a reset._
|
|
9152
|
+
`;
|
|
9153
|
+
var README_TYPE_SECTIONS = {
|
|
9154
|
+
coding: () => `
|
|
9155
|
+
## Architecture
|
|
9156
|
+
|
|
9157
|
+
_Components, data flow, key decisions._
|
|
9158
|
+
|
|
9159
|
+
## Deployment
|
|
9160
|
+
|
|
9161
|
+
_Environments, deploy steps, rollback._
|
|
9162
|
+
|
|
9163
|
+
## Commands
|
|
9164
|
+
|
|
9165
|
+
_Build, test, deploy \u2014 kept here so they survive a reset._
|
|
9154
9166
|
`,
|
|
9167
|
+
research: () => `
|
|
9168
|
+
## Sources
|
|
9169
|
+
|
|
9170
|
+
_Papers, articles, datasets, APIs._
|
|
9171
|
+
|
|
9172
|
+
## Findings
|
|
9173
|
+
|
|
9174
|
+
_Key findings, evidence, recommendations._
|
|
9175
|
+
`,
|
|
9176
|
+
marketing: () => `
|
|
9177
|
+
## Campaigns
|
|
9178
|
+
|
|
9179
|
+
_Active campaigns, audiences, channels._
|
|
9180
|
+
|
|
9181
|
+
## Metrics
|
|
9182
|
+
|
|
9183
|
+
_KPIs, targets, tracking dashboards._
|
|
9184
|
+
`,
|
|
9185
|
+
general: () => ""
|
|
9186
|
+
};
|
|
9187
|
+
var BASE_TEMPLATES = {
|
|
9188
|
+
"README.md": (name, type) => {
|
|
9189
|
+
const universal = README_UNIVERSAL(name);
|
|
9190
|
+
const extra = type ? README_TYPE_SECTIONS[type](name) : "";
|
|
9191
|
+
return universal + extra;
|
|
9192
|
+
},
|
|
9155
9193
|
"STATUS.md": (name) => `# Status: ${name}
|
|
9156
9194
|
|
|
9157
9195
|
## Last done (UTC)
|
|
9158
9196
|
|
|
9159
9197
|
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
9160
|
-
|
|
9161
9198
|
Topic created. Waiting for first instructions.
|
|
9162
9199
|
|
|
9163
9200
|
## Next actions (now)
|
|
@@ -9167,34 +9204,14 @@ _None yet._
|
|
|
9167
9204
|
## Upcoming actions
|
|
9168
9205
|
|
|
9169
9206
|
_None yet._
|
|
9170
|
-
`,
|
|
9171
|
-
"TODO.md": (name) => `# TODO: ${name}
|
|
9172
9207
|
|
|
9173
9208
|
## Backlog
|
|
9174
9209
|
|
|
9175
9210
|
- [T-1] _e.g. Set up project scaffolding_
|
|
9176
|
-
- [T-2] _Waiting for next task_
|
|
9177
|
-
- [T-3] _Waiting for next task_
|
|
9178
9211
|
|
|
9179
9212
|
## Completed
|
|
9180
9213
|
|
|
9181
9214
|
_None yet._
|
|
9182
|
-
`,
|
|
9183
|
-
"COMMANDS.md": (name) => `# Commands: ${name}
|
|
9184
|
-
|
|
9185
|
-
_Build, deploy, test, and other commands for this topic. Kept here so they're not lost on reset._
|
|
9186
|
-
`,
|
|
9187
|
-
"LINKS.md": (name) => `# Links: ${name}
|
|
9188
|
-
|
|
9189
|
-
_URLs, paths, and service endpoints for this topic._
|
|
9190
|
-
`,
|
|
9191
|
-
"CRON.md": (name) => `# Cron: ${name}
|
|
9192
|
-
|
|
9193
|
-
_Cron job IDs and schedules for this topic._
|
|
9194
|
-
`,
|
|
9195
|
-
"NOTES.md": (name) => `# Notes: ${name}
|
|
9196
|
-
|
|
9197
|
-
_Anything worth remembering about this topic._
|
|
9198
9215
|
`,
|
|
9199
9216
|
"LEARNINGS.md": (name) => `# Learnings: ${name}
|
|
9200
9217
|
|
|
@@ -9202,32 +9219,6 @@ _Hard-won insights, mistakes, and workarounds._
|
|
|
9202
9219
|
_Agent prepends here automatically. Most recent entries first._
|
|
9203
9220
|
`
|
|
9204
9221
|
};
|
|
9205
|
-
var OVERLAY_TEMPLATES = {
|
|
9206
|
-
"ARCHITECTURE.md": (name) => `# Architecture: ${name}
|
|
9207
|
-
|
|
9208
|
-
_Components, data flow, dependencies, and design decisions._
|
|
9209
|
-
`,
|
|
9210
|
-
"DEPLOY.md": (name) => `# Deployment: ${name}
|
|
9211
|
-
|
|
9212
|
-
_Environments, deployment steps, rollback procedures, and infra details._
|
|
9213
|
-
`,
|
|
9214
|
-
"SOURCES.md": (name) => `# Sources: ${name}
|
|
9215
|
-
|
|
9216
|
-
_Papers, articles, datasets, APIs, and other reference material._
|
|
9217
|
-
`,
|
|
9218
|
-
"FINDINGS.md": (name) => `# Findings: ${name}
|
|
9219
|
-
|
|
9220
|
-
_Conclusions, insights, data summaries, and recommendations._
|
|
9221
|
-
`,
|
|
9222
|
-
"CAMPAIGNS.md": (name) => `# Campaigns: ${name}
|
|
9223
|
-
|
|
9224
|
-
_Active campaigns, target audiences, channels, timelines, and budgets._
|
|
9225
|
-
`,
|
|
9226
|
-
"METRICS.md": (name) => `# Metrics: ${name}
|
|
9227
|
-
|
|
9228
|
-
_KPIs, conversion rates, engagement stats, and performance data._
|
|
9229
|
-
`
|
|
9230
|
-
};
|
|
9231
9222
|
var CAPSULE_FILE_MODE = 416;
|
|
9232
9223
|
function scaffoldCapsule(projectsBase, slug, name, type) {
|
|
9233
9224
|
const capsuleDir = path4.join(projectsBase, slug);
|
|
@@ -9242,15 +9233,7 @@ function scaffoldCapsule(projectsBase, slug, name, type) {
|
|
|
9242
9233
|
const templateFn = BASE_TEMPLATES[file];
|
|
9243
9234
|
if (templateFn) {
|
|
9244
9235
|
const filePath = path4.join(capsuleDir, file);
|
|
9245
|
-
fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
|
|
9246
|
-
}
|
|
9247
|
-
}
|
|
9248
|
-
const overlays = OVERLAY_FILES[type];
|
|
9249
|
-
for (const file of overlays) {
|
|
9250
|
-
const templateFn = OVERLAY_TEMPLATES[file];
|
|
9251
|
-
if (templateFn) {
|
|
9252
|
-
const filePath = path4.join(capsuleDir, file);
|
|
9253
|
-
fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
|
|
9236
|
+
fs4.writeFileSync(filePath, templateFn(name, type), { mode: CAPSULE_FILE_MODE });
|
|
9254
9237
|
}
|
|
9255
9238
|
}
|
|
9256
9239
|
}
|
|
@@ -9271,19 +9254,28 @@ function upgradeCapsule(projectsBase, slug, name, type, currentVersion) {
|
|
|
9271
9254
|
if (!fs4.existsSync(filePath)) {
|
|
9272
9255
|
const templateFn = BASE_TEMPLATES[file];
|
|
9273
9256
|
if (templateFn) {
|
|
9274
|
-
fs4.writeFileSync(filePath, templateFn(name), { mode: CAPSULE_FILE_MODE });
|
|
9257
|
+
fs4.writeFileSync(filePath, templateFn(name, type), { mode: CAPSULE_FILE_MODE });
|
|
9275
9258
|
addedFiles.push(file);
|
|
9276
9259
|
}
|
|
9277
9260
|
}
|
|
9278
9261
|
}
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
fs4.writeFileSync(
|
|
9286
|
-
|
|
9262
|
+
if (currentVersion < 4) {
|
|
9263
|
+
const statusPath = path4.join(capsuleDir, "STATUS.md");
|
|
9264
|
+
if (fs4.existsSync(statusPath)) {
|
|
9265
|
+
const content = fs4.readFileSync(statusPath, "utf-8");
|
|
9266
|
+
if (!content.includes("## Backlog")) {
|
|
9267
|
+
const appendix = "\n## Backlog\n\n- [T-1] _e.g. Set up project scaffolding_\n\n## Completed\n\n_None yet._\n";
|
|
9268
|
+
fs4.writeFileSync(statusPath, content.trimEnd() + "\n" + appendix, { mode: CAPSULE_FILE_MODE });
|
|
9269
|
+
}
|
|
9270
|
+
}
|
|
9271
|
+
const readmePath = path4.join(capsuleDir, "README.md");
|
|
9272
|
+
if (fs4.existsSync(readmePath)) {
|
|
9273
|
+
const content = fs4.readFileSync(readmePath, "utf-8");
|
|
9274
|
+
if (content.includes("_Describe what this topic is about._") && !content.includes("## Goal")) {
|
|
9275
|
+
const templateFn = BASE_TEMPLATES["README.md"];
|
|
9276
|
+
if (templateFn) {
|
|
9277
|
+
fs4.writeFileSync(readmePath, templateFn(name, type), { mode: CAPSULE_FILE_MODE });
|
|
9278
|
+
}
|
|
9287
9279
|
}
|
|
9288
9280
|
}
|
|
9289
9281
|
}
|
|
@@ -9644,8 +9636,8 @@ Context hierarchy (strictly ordered):
|
|
|
9644
9636
|
|
|
9645
9637
|
Determinism rules:
|
|
9646
9638
|
- After /reset, /new, or context compaction: ALWAYS re-read your topic files
|
|
9647
|
-
first \u2014 STATUS.md, then
|
|
9648
|
-
|
|
9639
|
+
first \u2014 STATUS.md, then LEARNINGS.md (last 20 entries), then README.md for
|
|
9640
|
+
topic context \u2014 before doing anything else. These are your ground truth.
|
|
9649
9641
|
Do not rely on summarized memory or workspace-level files for this topic's
|
|
9650
9642
|
tasks, status, or goals.
|
|
9651
9643
|
- Before context compaction or when the conversation is long: proactively
|
|
@@ -9653,13 +9645,12 @@ Determinism rules:
|
|
|
9653
9645
|
"Next actions (now)") so compaction cannot erase critical state.
|
|
9654
9646
|
Use the standard file write tool directly \u2014 do not route through /tm.
|
|
9655
9647
|
- Keep STATUS.md accurate: always maintain "Last done (UTC)", "Next actions (now)",
|
|
9656
|
-
|
|
9657
|
-
-
|
|
9658
|
-
|
|
9659
|
-
- If automation/cron is involved, record job IDs + schedules in CRON.md.
|
|
9660
|
-
- Task IDs (e.g., [T-1]) must stay consistent between STATUS.md and TODO.md.
|
|
9648
|
+
"Upcoming actions", "Backlog", and "Completed".
|
|
9649
|
+
- Task IDs (e.g., [T-1]) must stay consistent across STATUS.md sections.
|
|
9650
|
+
Move completed tasks from Backlog to Completed.
|
|
9661
9651
|
- STATUS.md has two priority sections: "Next actions (now)" for immediate work
|
|
9662
9652
|
and "Upcoming actions" for the near-future pipeline.
|
|
9653
|
+
- Key resources, commands, and context belong in README.md.
|
|
9663
9654
|
|
|
9664
9655
|
Learning capture:
|
|
9665
9656
|
- When you discover something unexpected, a mistake, a workaround, or a
|
|
@@ -10368,19 +10359,6 @@ function runCapsuleChecks(entry, projectsBase) {
|
|
|
10368
10359
|
check(Severity.ERROR, "statusMissing", "Status file is missing", true, "Run /tm upgrade to recreate it")
|
|
10369
10360
|
);
|
|
10370
10361
|
}
|
|
10371
|
-
if (!fs8.existsSync(path8.join(capsuleDir, "TODO.md"))) {
|
|
10372
|
-
results.push(
|
|
10373
|
-
check(Severity.WARN, "todoMissing", "TODO file is missing", true, "Run /tm upgrade to recreate it")
|
|
10374
|
-
);
|
|
10375
|
-
}
|
|
10376
|
-
const overlays = OVERLAY_FILES[entry.type] ?? [];
|
|
10377
|
-
for (const file of overlays) {
|
|
10378
|
-
if (!fs8.existsSync(path8.join(capsuleDir, file))) {
|
|
10379
|
-
results.push(
|
|
10380
|
-
check(Severity.INFO, `overlayMissing:${file}`, `Optional overlay ${file} missing for type "${entry.type}"`, true)
|
|
10381
|
-
);
|
|
10382
|
-
}
|
|
10383
|
-
}
|
|
10384
10362
|
if (entry.capsuleVersion < CAPSULE_VERSION) {
|
|
10385
10363
|
results.push(
|
|
10386
10364
|
check(
|
|
@@ -10457,7 +10435,8 @@ function runStatusQualityChecks(statusContent, entry) {
|
|
|
10457
10435
|
}
|
|
10458
10436
|
return results;
|
|
10459
10437
|
}
|
|
10460
|
-
|
|
10438
|
+
var BACKLOG_RE = /^##\s*Backlog/im;
|
|
10439
|
+
function runNextVsBacklogChecks(statusContent) {
|
|
10461
10440
|
const results = [];
|
|
10462
10441
|
const nextActionsIndex = statusContent.search(NEXT_ACTIONS_RE);
|
|
10463
10442
|
if (nextActionsIndex < 0) return results;
|
|
@@ -10466,14 +10445,19 @@ function runNextVsTodoChecks(statusContent, todoContent) {
|
|
|
10466
10445
|
const nextActionsSection = nextSectionIndex > 0 ? sectionAfter.slice(0, nextSectionIndex) : sectionAfter;
|
|
10467
10446
|
const nextTaskIds = nextActionsSection.match(TASK_ID_RE) ?? [];
|
|
10468
10447
|
if (nextTaskIds.length === 0) return results;
|
|
10469
|
-
const
|
|
10470
|
-
|
|
10448
|
+
const backlogIndex = statusContent.search(BACKLOG_RE);
|
|
10449
|
+
if (backlogIndex < 0) return results;
|
|
10450
|
+
const backlogAfter = statusContent.slice(backlogIndex);
|
|
10451
|
+
const backlogNextSection = backlogAfter.indexOf("\n## ", 1);
|
|
10452
|
+
const backlogSection = backlogNextSection > 0 ? backlogAfter.slice(0, backlogNextSection) : backlogAfter;
|
|
10453
|
+
const backlogTaskIds = new Set(backlogSection.match(TASK_ID_RE) ?? []);
|
|
10454
|
+
const missing = nextTaskIds.filter((id) => !backlogTaskIds.has(id));
|
|
10471
10455
|
if (missing.length >= 2) {
|
|
10472
10456
|
results.push(
|
|
10473
10457
|
check(
|
|
10474
10458
|
Severity.WARN,
|
|
10475
|
-
"
|
|
10476
|
-
`${missing.length} tasks referenced in next actions don't exist in the
|
|
10459
|
+
"nextNotInBacklog",
|
|
10460
|
+
`${missing.length} tasks referenced in next actions don't exist in the backlog: ${missing.join(", ")}`,
|
|
10477
10461
|
false,
|
|
10478
10462
|
"The AI will clean these up on next interaction"
|
|
10479
10463
|
)
|
|
@@ -10481,62 +10465,18 @@ function runNextVsTodoChecks(statusContent, todoContent) {
|
|
|
10481
10465
|
}
|
|
10482
10466
|
return results;
|
|
10483
10467
|
}
|
|
10484
|
-
function
|
|
10485
|
-
const results = [];
|
|
10486
|
-
if (entry.type === "coding") {
|
|
10487
|
-
const commandsContent = capsuleFiles.get("COMMANDS.md");
|
|
10488
|
-
if (commandsContent !== void 0 && isEffectivelyEmpty(commandsContent)) {
|
|
10489
|
-
results.push(
|
|
10490
|
-
check(Severity.INFO, "commandsEmpty", "COMMANDS.md is empty for a coding topic", false, "Add build/test/deploy commands to COMMANDS.md")
|
|
10491
|
-
);
|
|
10492
|
-
}
|
|
10493
|
-
}
|
|
10494
|
-
if (entry.type === "coding" || entry.type === "research") {
|
|
10495
|
-
const linksContent = capsuleFiles.get("LINKS.md");
|
|
10496
|
-
if (linksContent !== void 0 && isEffectivelyEmpty(linksContent)) {
|
|
10497
|
-
results.push(
|
|
10498
|
-
check(Severity.INFO, "linksEmpty", "LINKS.md is empty for a coding/research topic", false, "Add URLs and endpoints to LINKS.md")
|
|
10499
|
-
);
|
|
10500
|
-
}
|
|
10501
|
-
}
|
|
10502
|
-
return results;
|
|
10503
|
-
}
|
|
10504
|
-
function isEffectivelyEmpty(content) {
|
|
10505
|
-
const stripped = content.replace(/^#.*$/gm, "").replace(/^_.*_$/gm, "").replace(/\s+/g, "").trim();
|
|
10506
|
-
return stripped.length === 0;
|
|
10507
|
-
}
|
|
10508
|
-
var JOB_ID_RE = /[a-zA-Z0-9_-]{8,}/;
|
|
10509
|
-
function runCronChecks(cronContent, cronJobsPath) {
|
|
10468
|
+
function runUnfilledContextCheck(capsuleFiles) {
|
|
10510
10469
|
const results = [];
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
const hasJobIds = lines.some((line) => JOB_ID_RE.test(line));
|
|
10514
|
-
if (!hasJobIds) {
|
|
10470
|
+
const readmeContent = capsuleFiles.get("README.md");
|
|
10471
|
+
if (readmeContent && readmeContent.includes("_Describe what this topic is about._")) {
|
|
10515
10472
|
results.push(
|
|
10516
|
-
check(
|
|
10473
|
+
check(
|
|
10474
|
+
Severity.INFO,
|
|
10475
|
+
"contextUnfilled",
|
|
10476
|
+
"Topic context is empty \u2014 tell the AI about your project to get better help.",
|
|
10477
|
+
false
|
|
10478
|
+
)
|
|
10517
10479
|
);
|
|
10518
|
-
return results;
|
|
10519
|
-
}
|
|
10520
|
-
if (cronJobsPath && fs8.existsSync(cronJobsPath)) {
|
|
10521
|
-
try {
|
|
10522
|
-
const jobsRaw = fs8.readFileSync(cronJobsPath, "utf-8");
|
|
10523
|
-
const jobs = JSON.parse(jobsRaw);
|
|
10524
|
-
const knownJobIds = new Set(Object.keys(jobs));
|
|
10525
|
-
for (const line of lines) {
|
|
10526
|
-
const match = line.match(JOB_ID_RE);
|
|
10527
|
-
if (match && !knownJobIds.has(match[0])) {
|
|
10528
|
-
results.push(
|
|
10529
|
-
check(
|
|
10530
|
-
Severity.WARN,
|
|
10531
|
-
"cronJobNotFound",
|
|
10532
|
-
`Scheduled job "${match[0]}" not found in the jobs registry`,
|
|
10533
|
-
false
|
|
10534
|
-
)
|
|
10535
|
-
);
|
|
10536
|
-
}
|
|
10537
|
-
}
|
|
10538
|
-
} catch {
|
|
10539
|
-
}
|
|
10540
10480
|
}
|
|
10541
10481
|
return results;
|
|
10542
10482
|
}
|
|
@@ -10634,7 +10574,7 @@ function runPostErrorCheck(entry) {
|
|
|
10634
10574
|
}
|
|
10635
10575
|
return results;
|
|
10636
10576
|
}
|
|
10637
|
-
function runAllChecksForTopic(entry, projectsBase, includeContent, registry
|
|
10577
|
+
function runAllChecksForTopic(entry, projectsBase, includeContent, registry) {
|
|
10638
10578
|
const results = [];
|
|
10639
10579
|
const capsuleDir = path8.join(projectsBase, entry.slug);
|
|
10640
10580
|
results.push(...runRegistryChecks(entry, projectsBase));
|
|
@@ -10645,16 +10585,9 @@ function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cro
|
|
|
10645
10585
|
const statusContent = capsuleFiles.get("STATUS.md");
|
|
10646
10586
|
if (statusContent) {
|
|
10647
10587
|
results.push(...runStatusQualityChecks(statusContent, entry));
|
|
10648
|
-
|
|
10649
|
-
if (todoContent) {
|
|
10650
|
-
results.push(...runNextVsTodoChecks(statusContent, todoContent));
|
|
10651
|
-
}
|
|
10652
|
-
}
|
|
10653
|
-
results.push(...runCommandsLinksChecks(entry, capsuleFiles));
|
|
10654
|
-
const cronContent = capsuleFiles.get("CRON.md");
|
|
10655
|
-
if (cronContent) {
|
|
10656
|
-
results.push(...runCronChecks(cronContent, cronJobsPath));
|
|
10588
|
+
results.push(...runNextVsBacklogChecks(statusContent));
|
|
10657
10589
|
}
|
|
10590
|
+
results.push(...runUnfilledContextCheck(capsuleFiles));
|
|
10658
10591
|
if (includeContent && registry) {
|
|
10659
10592
|
results.push(...runConfigChecks(entry, includeContent, registry));
|
|
10660
10593
|
}
|
|
@@ -10665,7 +10598,7 @@ function runAllChecksForTopic(entry, projectsBase, includeContent, registry, cro
|
|
|
10665
10598
|
return results;
|
|
10666
10599
|
}
|
|
10667
10600
|
var BACKUP_DIR = ".tm-backup";
|
|
10668
|
-
var BACKUP_FILES = ["STATUS.md"
|
|
10601
|
+
var BACKUP_FILES = ["STATUS.md"];
|
|
10669
10602
|
function backupCapsuleIfHealthy(projectsBase, slug, results) {
|
|
10670
10603
|
const hasIssues = results.some((r) => r.severity === Severity.ERROR || r.severity === Severity.WARN);
|
|
10671
10604
|
if (hasIssues) return;
|
|
@@ -10684,22 +10617,7 @@ function backupCapsuleIfHealthy(projectsBase, slug, results) {
|
|
|
10684
10617
|
}
|
|
10685
10618
|
function readCapsuleFiles(capsuleDir) {
|
|
10686
10619
|
const files = /* @__PURE__ */ new Map();
|
|
10687
|
-
const filenames = [
|
|
10688
|
-
"STATUS.md",
|
|
10689
|
-
"TODO.md",
|
|
10690
|
-
"COMMANDS.md",
|
|
10691
|
-
"LINKS.md",
|
|
10692
|
-
"CRON.md",
|
|
10693
|
-
"NOTES.md",
|
|
10694
|
-
"README.md",
|
|
10695
|
-
"LEARNINGS.md",
|
|
10696
|
-
"ARCHITECTURE.md",
|
|
10697
|
-
"DEPLOY.md",
|
|
10698
|
-
"SOURCES.md",
|
|
10699
|
-
"FINDINGS.md",
|
|
10700
|
-
"CAMPAIGNS.md",
|
|
10701
|
-
"METRICS.md"
|
|
10702
|
-
];
|
|
10620
|
+
const filenames = ["README.md", "STATUS.md", "LEARNINGS.md"];
|
|
10703
10621
|
for (const name of filenames) {
|
|
10704
10622
|
const filePath = path8.join(capsuleDir, name);
|
|
10705
10623
|
try {
|
|
@@ -10744,13 +10662,11 @@ async function handleDoctor(ctx) {
|
|
|
10744
10662
|
}
|
|
10745
10663
|
} catch {
|
|
10746
10664
|
}
|
|
10747
|
-
const cronJobsPath = path9.join(configDir, "cron", "jobs.json");
|
|
10748
10665
|
const results = runAllChecksForTopic(
|
|
10749
10666
|
entry,
|
|
10750
10667
|
projectsBase,
|
|
10751
10668
|
includeContent,
|
|
10752
|
-
registry
|
|
10753
|
-
cronJobsPath
|
|
10669
|
+
registry
|
|
10754
10670
|
);
|
|
10755
10671
|
backupCapsuleIfHealthy(projectsBase, entry.slug, results);
|
|
10756
10672
|
const reportText = buildDoctorReport(entry.name, results);
|
|
@@ -10803,9 +10719,8 @@ async function handleDailyReport(ctx) {
|
|
|
10803
10719
|
return { text: "Topic files not found. Run /tm init to set up this topic." };
|
|
10804
10720
|
}
|
|
10805
10721
|
const statusContent = readFileOrNull(path10.join(capsuleDir, "STATUS.md"));
|
|
10806
|
-
const todoContent = readFileOrNull(path10.join(capsuleDir, "TODO.md"));
|
|
10807
10722
|
const doneContent = extractDoneSection(statusContent);
|
|
10808
|
-
const blockers = extractBlockers(
|
|
10723
|
+
const blockers = extractBlockers(statusContent);
|
|
10809
10724
|
const nextContent = extractNextActions(statusContent);
|
|
10810
10725
|
const reportData = {
|
|
10811
10726
|
name: entry.name,
|
|
@@ -10859,9 +10774,11 @@ function extractDoneSection(statusContent) {
|
|
|
10859
10774
|
const text = match[1]?.trim();
|
|
10860
10775
|
return text || "Empty.";
|
|
10861
10776
|
}
|
|
10862
|
-
function extractBlockers(
|
|
10863
|
-
if (!
|
|
10864
|
-
const
|
|
10777
|
+
function extractBlockers(statusContent) {
|
|
10778
|
+
if (!statusContent) return "No tasks recorded yet.";
|
|
10779
|
+
const backlogMatch = statusContent.match(/^##\s*Backlog\b.*\n((?:(?!\n## )[\s\S])*)/im);
|
|
10780
|
+
const backlogSection = backlogMatch ? backlogMatch[1] ?? "" : statusContent;
|
|
10781
|
+
const lines = backlogSection.split("\n");
|
|
10865
10782
|
const blockerLines = lines.filter(
|
|
10866
10783
|
(l) => /\[BLOCKED\]/i.test(l) || /\bblocked\b/i.test(l)
|
|
10867
10784
|
);
|
|
@@ -11021,13 +10938,13 @@ async function handleDoctorAll(ctx) {
|
|
|
11021
10938
|
}
|
|
11022
10939
|
} catch {
|
|
11023
10940
|
}
|
|
11024
|
-
const cronJobsPath = path11.join(configDir, "cron", "jobs.json");
|
|
11025
10941
|
const allEntries = Object.entries(registry.topics);
|
|
11026
10942
|
const reports = [];
|
|
11027
10943
|
const errors = [];
|
|
11028
10944
|
const checkedTopics = [];
|
|
11029
10945
|
const skippedTopics = [];
|
|
11030
10946
|
const groupPostResults = /* @__PURE__ */ new Map();
|
|
10947
|
+
const upgradedSlugs = /* @__PURE__ */ new Set();
|
|
11031
10948
|
for (const [_key, entry] of allEntries) {
|
|
11032
10949
|
const capsuleDir = path11.join(projectsBase, entry.slug);
|
|
11033
10950
|
const statusForEligibility = readFileOrNull(path11.join(capsuleDir, "STATUS.md"));
|
|
@@ -11038,12 +10955,22 @@ async function handleDoctorAll(ctx) {
|
|
|
11038
10955
|
continue;
|
|
11039
10956
|
}
|
|
11040
10957
|
try {
|
|
10958
|
+
if (entry.capsuleVersion < CAPSULE_VERSION) {
|
|
10959
|
+
const oldVersion = entry.capsuleVersion;
|
|
10960
|
+
try {
|
|
10961
|
+
upgradeCapsule(projectsBase, entry.slug, entry.name, entry.type, entry.capsuleVersion);
|
|
10962
|
+
upgradedSlugs.add(entry.slug);
|
|
10963
|
+
logger.info(`[doctor-all] Auto-upgraded ${entry.slug} v${oldVersion} \u2192 v${CAPSULE_VERSION}`);
|
|
10964
|
+
} catch (err) {
|
|
10965
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10966
|
+
logger.error(`[doctor-all] Auto-upgrade failed for ${entry.slug}: ${msg}`);
|
|
10967
|
+
}
|
|
10968
|
+
}
|
|
11041
10969
|
const results = runAllChecksForTopic(
|
|
11042
10970
|
entry,
|
|
11043
10971
|
projectsBase,
|
|
11044
10972
|
includeContent,
|
|
11045
|
-
registry
|
|
11046
|
-
cronJobsPath
|
|
10973
|
+
registry
|
|
11047
10974
|
);
|
|
11048
10975
|
const isSpam = entry.consecutiveSilentDoctors >= SPAM_THRESHOLD;
|
|
11049
10976
|
if (isSpam) {
|
|
@@ -11145,6 +11072,9 @@ async function handleDoctorAll(ctx) {
|
|
|
11145
11072
|
entry.consecutiveSilentDoctors++;
|
|
11146
11073
|
}
|
|
11147
11074
|
entry.lastDoctorRunAt = now.toISOString();
|
|
11075
|
+
if (upgradedSlugs.has(entry.slug)) {
|
|
11076
|
+
entry.capsuleVersion = CAPSULE_VERSION;
|
|
11077
|
+
}
|
|
11148
11078
|
if (entry.consecutiveSilentDoctors >= SPAM_THRESHOLD) {
|
|
11149
11079
|
entry.snoozeUntil = new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString();
|
|
11150
11080
|
entry.status = "snoozed";
|
|
@@ -11244,10 +11174,10 @@ function hasBlockers(blockers) {
|
|
|
11244
11174
|
return blockers !== "None." && blockers !== "No tasks recorded yet.";
|
|
11245
11175
|
}
|
|
11246
11176
|
function formatStatus(data) {
|
|
11247
|
-
const { name, type, statusContent,
|
|
11177
|
+
const { name, type, statusContent, expanded } = data;
|
|
11248
11178
|
const timestamp = extractTimestamp(statusContent);
|
|
11249
11179
|
const nextRaw = extractSection(statusContent, NEXT_ACTIONS_RE2);
|
|
11250
|
-
const blockers = extractBlockers(
|
|
11180
|
+
const blockers = extractBlockers(statusContent);
|
|
11251
11181
|
const doneMatch = statusContent.match(LAST_DONE_RE2);
|
|
11252
11182
|
let lastDoneBody = "";
|
|
11253
11183
|
if (doneMatch) {
|
|
@@ -11312,13 +11242,11 @@ async function handleStatus2(ctx, args) {
|
|
|
11312
11242
|
const expanded = args.trim() === "--expanded";
|
|
11313
11243
|
try {
|
|
11314
11244
|
const statusContent = fs12.readFileSync(statusPath, "utf-8");
|
|
11315
|
-
const todoContent = readFileOrNull(path12.join(capsuleDir, "TODO.md"));
|
|
11316
11245
|
return {
|
|
11317
11246
|
text: truncateMessage(formatStatus({
|
|
11318
11247
|
name: entry.name,
|
|
11319
11248
|
type: entry.type,
|
|
11320
11249
|
statusContent,
|
|
11321
|
-
todoContent,
|
|
11322
11250
|
expanded
|
|
11323
11251
|
}))
|
|
11324
11252
|
};
|
package/package.json
CHANGED
package/skills/tm/SKILL.md
CHANGED
|
@@ -15,14 +15,36 @@ If you detect any of these conditions, invoke `topic_manager` proactively:
|
|
|
15
15
|
|
|
16
16
|
1. **After /reset, /new, or context compaction**: call `topic_manager` with
|
|
17
17
|
command "status" to re-read the topic files and rehydrate context.
|
|
18
|
-
Rehydration order: STATUS.md,
|
|
19
|
-
then
|
|
18
|
+
Rehydration order: STATUS.md, then LEARNINGS.md (last 20 entries),
|
|
19
|
+
then README.md for topic context.
|
|
20
20
|
2. **Before context gets large**: proactively flush current progress to
|
|
21
21
|
STATUS.md using the standard file write tool (update "Last done (UTC)"
|
|
22
22
|
and "Next actions (now)"). Do NOT route this through /tm — write directly.
|
|
23
23
|
3. **When you notice a topic has no persistent memory**: suggest `/tm init`.
|
|
24
24
|
4. **When you discover something unexpected** (a mistake, workaround, or
|
|
25
25
|
constraint): prepend a dated entry to LEARNINGS.md in the topic folder.
|
|
26
|
+
5. **When you see a fresh topic** (STATUS.md says "Waiting for first instructions"
|
|
27
|
+
or README.md has unfilled context sections like `_Describe what this topic is about._`),
|
|
28
|
+
proactively ask the user 2-3 questions about what this topic is about, what the
|
|
29
|
+
goal is, and where the key resources are. Persist answers to README.md immediately.
|
|
30
|
+
6. **When the user shares important project information** that would be needed
|
|
31
|
+
after a session reset, persist it immediately to the appropriate section
|
|
32
|
+
in README.md. Don't wait for the user to ask — this is the first context
|
|
33
|
+
lost on reset.
|
|
34
|
+
|
|
35
|
+
Examples by topic type:
|
|
36
|
+
- **Coding**: repository paths, runtime/data paths, branch names, service URLs,
|
|
37
|
+
environment details, build/test/deploy commands → README.md sections
|
|
38
|
+
(Architecture, Deployment, Commands, Key resources)
|
|
39
|
+
- **Research**: key sources, data locations, API endpoints, methodology decisions
|
|
40
|
+
→ README.md sections (Sources, Findings, Key resources)
|
|
41
|
+
- **Marketing**: campaign URLs, analytics dashboards, social accounts, brand
|
|
42
|
+
guidelines location → README.md sections (Campaigns, Metrics, Key resources)
|
|
43
|
+
- **General / any type**: reference URLs, contacts, key decisions, file paths
|
|
44
|
+
→ README.md sections (Key resources)
|
|
45
|
+
|
|
46
|
+
Rule of thumb: if losing this information would cause the agent to make a
|
|
47
|
+
wrong assumption after reset, it must be written down now.
|
|
26
48
|
|
|
27
49
|
## Autopilot context
|
|
28
50
|
|