wayfind 2.0.38 → 2.0.39
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/bin/team-context.js +211 -2
- package/package.json +1 -1
package/bin/team-context.js
CHANGED
|
@@ -4664,6 +4664,194 @@ function runCheckVersion() {
|
|
|
4664
4664
|
}, CLI_USER);
|
|
4665
4665
|
}
|
|
4666
4666
|
|
|
4667
|
+
// ── Container doctor ────────────────────────────────────────────────────────
|
|
4668
|
+
|
|
4669
|
+
/**
|
|
4670
|
+
* Detect if we're running inside a Docker container.
|
|
4671
|
+
*/
|
|
4672
|
+
function isRunningInContainer() {
|
|
4673
|
+
try {
|
|
4674
|
+
return fs.existsSync('/.dockerenv');
|
|
4675
|
+
} catch {
|
|
4676
|
+
return false;
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4680
|
+
/**
|
|
4681
|
+
* Container-specific health checks. Prints PASS/WARN lines and exits with
|
|
4682
|
+
* appropriate code. Useful as a post-startup self-check or via
|
|
4683
|
+
* `docker exec wayfind npx wayfind doctor --container`.
|
|
4684
|
+
*/
|
|
4685
|
+
async function runContainerDoctor() {
|
|
4686
|
+
const GREEN = '\x1b[32m', YELLOW = '\x1b[33m', RED = '\x1b[31m', RESET = '\x1b[0m';
|
|
4687
|
+
const pass = (msg) => console.log(`${GREEN}PASS${RESET} ${msg}`);
|
|
4688
|
+
const warn = (msg) => console.log(`${YELLOW}WARN${RESET} ${msg}`);
|
|
4689
|
+
let issues = 0;
|
|
4690
|
+
|
|
4691
|
+
console.log('');
|
|
4692
|
+
console.log('Wayfind — Container Doctor');
|
|
4693
|
+
console.log('══════════════════════════════');
|
|
4694
|
+
|
|
4695
|
+
// 1. Backend type — is SQLite active or JSON fallback?
|
|
4696
|
+
const storePath = path.join(EFFECTIVE_DIR, 'content-store');
|
|
4697
|
+
try {
|
|
4698
|
+
const storage = require('./storage/index.js');
|
|
4699
|
+
storage.getBackend(storePath);
|
|
4700
|
+
const info = storage.getBackendInfo(storePath);
|
|
4701
|
+
if (!info) {
|
|
4702
|
+
warn('Storage backend: not initialized');
|
|
4703
|
+
issues++;
|
|
4704
|
+
} else if (info.type === 'sqlite' && !info.fallback) {
|
|
4705
|
+
pass(`Storage backend: sqlite`);
|
|
4706
|
+
} else if (info.type === 'json' && info.fallback) {
|
|
4707
|
+
warn('Storage backend: JSON (fallback — SQLite failed to load)');
|
|
4708
|
+
console.log(' Install better-sqlite3 or rebuild the container image');
|
|
4709
|
+
issues++;
|
|
4710
|
+
} else if (info.type === 'json') {
|
|
4711
|
+
warn('Storage backend: JSON (not SQLite)');
|
|
4712
|
+
issues++;
|
|
4713
|
+
} else {
|
|
4714
|
+
pass(`Storage backend: ${info.type}`);
|
|
4715
|
+
}
|
|
4716
|
+
} catch (e) {
|
|
4717
|
+
warn(`Storage backend: error — ${e.message}`);
|
|
4718
|
+
issues++;
|
|
4719
|
+
}
|
|
4720
|
+
|
|
4721
|
+
// 2. Entry count — are there entries in the content store?
|
|
4722
|
+
let entryCount = 0;
|
|
4723
|
+
try {
|
|
4724
|
+
const storage = require('./storage/index.js');
|
|
4725
|
+
const backend = storage.getBackend(storePath);
|
|
4726
|
+
const idx = backend.loadIndex();
|
|
4727
|
+
if (idx && idx.entries) {
|
|
4728
|
+
entryCount = Object.keys(idx.entries).length;
|
|
4729
|
+
}
|
|
4730
|
+
if (entryCount > 0) {
|
|
4731
|
+
pass(`Content store: ${entryCount} entries`);
|
|
4732
|
+
} else {
|
|
4733
|
+
warn('Content store: 0 entries — no signals have been indexed');
|
|
4734
|
+
console.log(' Run: wayfind pull --all');
|
|
4735
|
+
issues++;
|
|
4736
|
+
}
|
|
4737
|
+
} catch (e) {
|
|
4738
|
+
warn(`Content store: error — ${e.message}`);
|
|
4739
|
+
issues++;
|
|
4740
|
+
}
|
|
4741
|
+
|
|
4742
|
+
// 3. Embedding coverage — what % of entries have embeddings?
|
|
4743
|
+
if (entryCount > 0) {
|
|
4744
|
+
try {
|
|
4745
|
+
const storage = require('./storage/index.js');
|
|
4746
|
+
const backend = storage.getBackend(storePath);
|
|
4747
|
+
const idx = backend.loadIndex();
|
|
4748
|
+
let total = 0, embedded = 0;
|
|
4749
|
+
if (idx && idx.entries) {
|
|
4750
|
+
for (const e of Object.values(idx.entries)) {
|
|
4751
|
+
total++;
|
|
4752
|
+
if (e.hasEmbedding) embedded++;
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
4755
|
+
const pct = total > 0 ? Math.round(100 * embedded / total) : 0;
|
|
4756
|
+
if (pct >= 50) {
|
|
4757
|
+
pass(`Embedding coverage: ${embedded}/${total} (${pct}%)`);
|
|
4758
|
+
} else {
|
|
4759
|
+
warn(`Embedding coverage: ${embedded}/${total} (${pct}%) — search quality degraded`);
|
|
4760
|
+
console.log(' Run: wayfind reindex');
|
|
4761
|
+
issues++;
|
|
4762
|
+
}
|
|
4763
|
+
} catch (e) {
|
|
4764
|
+
warn(`Embedding coverage: error — ${e.message}`);
|
|
4765
|
+
issues++;
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
|
|
4769
|
+
// 4. Signal freshness — are there signal files from today?
|
|
4770
|
+
const signalsDir = path.join(EFFECTIVE_DIR, 'signals');
|
|
4771
|
+
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
4772
|
+
try {
|
|
4773
|
+
if (!fs.existsSync(signalsDir)) {
|
|
4774
|
+
warn('Signal freshness: no signals directory');
|
|
4775
|
+
issues++;
|
|
4776
|
+
} else {
|
|
4777
|
+
const channels = fs.readdirSync(signalsDir).filter((f) => {
|
|
4778
|
+
try { return fs.statSync(path.join(signalsDir, f)).isDirectory(); } catch { return false; }
|
|
4779
|
+
});
|
|
4780
|
+
if (channels.length === 0) {
|
|
4781
|
+
warn('Signal freshness: no signal channels found');
|
|
4782
|
+
issues++;
|
|
4783
|
+
} else {
|
|
4784
|
+
let freshCount = 0;
|
|
4785
|
+
for (const ch of channels) {
|
|
4786
|
+
const chDir = path.join(signalsDir, ch);
|
|
4787
|
+
const files = fs.readdirSync(chDir).filter((f) => f.endsWith('.md')).sort().reverse();
|
|
4788
|
+
const newest = files[0] || '';
|
|
4789
|
+
// Signal files are named with date prefix, e.g. 2026-03-28-....md
|
|
4790
|
+
if (newest.startsWith(today)) {
|
|
4791
|
+
freshCount++;
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
if (freshCount === channels.length) {
|
|
4795
|
+
pass(`Signal freshness: ${freshCount}/${channels.length} channels have signals from today`);
|
|
4796
|
+
} else {
|
|
4797
|
+
warn(`Signal freshness: ${freshCount}/${channels.length} channels have signals from today`);
|
|
4798
|
+
console.log(' Run: wayfind pull --all');
|
|
4799
|
+
issues++;
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
} catch (e) {
|
|
4804
|
+
warn(`Signal freshness: error — ${e.message}`);
|
|
4805
|
+
issues++;
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4808
|
+
// 5. Slack bot / health endpoint — is /healthz responding?
|
|
4809
|
+
const healthPort = parseInt(process.env.TEAM_CONTEXT_HEALTH_PORT || '3141', 10);
|
|
4810
|
+
try {
|
|
4811
|
+
const healthResult = await new Promise((resolve) => {
|
|
4812
|
+
const req = http.get(`http://localhost:${healthPort}/healthz`, { timeout: 3000 }, (res) => {
|
|
4813
|
+
let body = '';
|
|
4814
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
4815
|
+
res.on('end', () => resolve({ status: res.statusCode, body }));
|
|
4816
|
+
});
|
|
4817
|
+
req.on('error', (e) => resolve({ status: 0, error: e.message }));
|
|
4818
|
+
req.on('timeout', () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
|
|
4819
|
+
});
|
|
4820
|
+
|
|
4821
|
+
if (healthResult.status === 200) {
|
|
4822
|
+
let detail = '';
|
|
4823
|
+
try {
|
|
4824
|
+
const data = JSON.parse(healthResult.body);
|
|
4825
|
+
if (data.slack && data.slack.connected) {
|
|
4826
|
+
detail = ' (Slack connected)';
|
|
4827
|
+
} else if (data.slack && !data.slack.connected) {
|
|
4828
|
+
detail = ' (Slack disconnected)';
|
|
4829
|
+
}
|
|
4830
|
+
} catch { /* ignore parse errors */ }
|
|
4831
|
+
pass(`Health endpoint: http://localhost:${healthPort}/healthz${detail}`);
|
|
4832
|
+
} else if (healthResult.status > 0) {
|
|
4833
|
+
warn(`Health endpoint: responded with ${healthResult.status}`);
|
|
4834
|
+
issues++;
|
|
4835
|
+
} else {
|
|
4836
|
+
warn(`Health endpoint: not reachable — ${healthResult.error}`);
|
|
4837
|
+
console.log(' Is the container running? Check: docker ps');
|
|
4838
|
+
issues++;
|
|
4839
|
+
}
|
|
4840
|
+
} catch (e) {
|
|
4841
|
+
warn(`Health endpoint: error — ${e.message}`);
|
|
4842
|
+
issues++;
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4845
|
+
console.log('');
|
|
4846
|
+
console.log('══════════════════════════════');
|
|
4847
|
+
if (issues === 0) {
|
|
4848
|
+
console.log(`${GREEN}All container checks passed${RESET}`);
|
|
4849
|
+
} else {
|
|
4850
|
+
console.log(`${YELLOW}${issues} issue(s) found${RESET}`);
|
|
4851
|
+
process.exit(1);
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4667
4855
|
// ── Command registry ────────────────────────────────────────────────────────
|
|
4668
4856
|
|
|
4669
4857
|
const COMMANDS = {
|
|
@@ -4777,6 +4965,22 @@ const COMMANDS = {
|
|
|
4777
4965
|
console.log('Recreating container with new image...');
|
|
4778
4966
|
spawnSync('docker', ['compose', 'up', '-d'], { cwd: composeDir, stdio: 'inherit' });
|
|
4779
4967
|
console.log('Container updated.');
|
|
4968
|
+
|
|
4969
|
+
// Post-deploy smoke check: scan container logs for warnings/errors
|
|
4970
|
+
console.log('\nRunning post-deploy smoke check (waiting 5s for container startup)...');
|
|
4971
|
+
spawnSync('sleep', ['5']);
|
|
4972
|
+
const logsResult = spawnSync('docker', ['logs', '--tail', '50', 'wayfind'], { stdio: 'pipe' });
|
|
4973
|
+
const logOutput = (logsResult.stdout || '').toString() + (logsResult.stderr || '').toString();
|
|
4974
|
+
const warningPatterns = /Warning:|Error:|failed|fallback/i;
|
|
4975
|
+
const logLines = logOutput.split('\n').filter(l => l.trim());
|
|
4976
|
+
const warnings = logLines.filter(l => warningPatterns.test(l));
|
|
4977
|
+
if (warnings.length > 0) {
|
|
4978
|
+
console.log('');
|
|
4979
|
+
warnings.forEach(w => console.log(` \u26A0 ${w.trim()}`));
|
|
4980
|
+
console.log('\n Post-deploy warnings detected \u2014 review above');
|
|
4981
|
+
} else {
|
|
4982
|
+
console.log(' Post-deploy check: no warnings');
|
|
4983
|
+
}
|
|
4780
4984
|
} else {
|
|
4781
4985
|
console.error('Docker pull failed — container not updated.');
|
|
4782
4986
|
}
|
|
@@ -4829,9 +5033,13 @@ const COMMANDS = {
|
|
|
4829
5033
|
run: (args) => runMigrateToPlugin(args),
|
|
4830
5034
|
},
|
|
4831
5035
|
doctor: {
|
|
4832
|
-
desc: 'Check your Wayfind installation for issues',
|
|
5036
|
+
desc: 'Check your Wayfind installation for issues (--container for container checks)',
|
|
4833
5037
|
run: (args) => {
|
|
4834
|
-
|
|
5038
|
+
if (args.includes('--container') || isRunningInContainer()) {
|
|
5039
|
+
runContainerDoctor();
|
|
5040
|
+
} else {
|
|
5041
|
+
spawn('bash', [path.join(ROOT, 'doctor.sh'), ...args]);
|
|
5042
|
+
}
|
|
4835
5043
|
},
|
|
4836
5044
|
},
|
|
4837
5045
|
version: {
|
|
@@ -5210,6 +5418,7 @@ function showHelp() {
|
|
|
5210
5418
|
console.log(' wayfind update Update from npm, re-sync hooks, update container');
|
|
5211
5419
|
console.log(' wayfind migrate-to-plugin Remove old hooks/commands — let the plugin handle them');
|
|
5212
5420
|
console.log(' wayfind doctor Check installation health');
|
|
5421
|
+
console.log(' wayfind doctor --container Container-specific health checks (auto-detected in Docker)');
|
|
5213
5422
|
console.log('');
|
|
5214
5423
|
console.log('Publishing:');
|
|
5215
5424
|
console.log(' wayfind sync-public Sync code to usewayfind/wayfind (triggers npm + Docker publish)');
|
package/package.json
CHANGED