@davesheffer/hunch 1.44.0 → 1.45.1
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 +6 -16
- package/dist/cli/index.js +153 -70
- package/dist/core/agenthook.d.ts +1 -0
- package/dist/core/agenthook.js +11 -0
- package/dist/core/escalations.d.ts +20 -0
- package/dist/core/escalations.js +47 -0
- package/dist/core/hookText.js +1 -1
- package/dist/integrations/claudemd.js +1 -1
- package/dist/integrations/gitignore.d.ts +16 -0
- package/dist/integrations/gitignore.js +30 -0
- package/dist/integrations/hooks.d.ts +20 -0
- package/dist/integrations/hooks.js +13 -6
- package/dist/integrations/providers.d.ts +9 -0
- package/dist/integrations/providers.js +73 -9
- package/dist/integrations/scaffold.js +1 -1
- package/dist/integrations/uninstall.d.ts +2 -0
- package/dist/integrations/uninstall.js +72 -11
- package/dist/mcp/leanToolList.d.ts +9 -0
- package/dist/mcp/leanToolList.js +89 -0
- package/dist/mcp/server.js +4 -2
- package/dist/serve/mcpHttp.js +2 -0
- package/package.json +2 -5
- package/server.json +2 -2
- package/tooling/md1-benchmark.mjs +1 -1
- package/tooling/competitive-watch.mjs +0 -142
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const repos = [
|
|
4
|
-
"davesheffer/hunch",
|
|
5
|
-
"gitmem-dev/gitmem",
|
|
6
|
-
"ismaelkedir/knowit",
|
|
7
|
-
"weigibbor/mnemo",
|
|
8
|
-
"oldskultxo/aictx",
|
|
9
|
-
"riponcm/projectmem",
|
|
10
|
-
"Cranot/roam-code",
|
|
11
|
-
"blackwell-systems/knowing",
|
|
12
|
-
"markmhendrickson/neotoma",
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
const distinctivePhrases = [
|
|
16
|
-
"Causal Merge Verdict",
|
|
17
|
-
"corrections become enforced",
|
|
18
|
-
"content-matched constraints",
|
|
19
|
-
"deterministic Change Gate for AI-assisted codebases",
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
let token = process.env.GITHUB_TOKEN?.trim();
|
|
23
|
-
// A rejected token must DEGRADE the run, never kill it: ambient CI/proxy tokens
|
|
24
|
-
// are often invalid for api.github.com, and the metadata half of this watch
|
|
25
|
-
// works unauthenticated. Flipped on the first 401 so every later call skips
|
|
26
|
-
// the bad credential instead of failing eight times.
|
|
27
|
-
let tokenRejected = false;
|
|
28
|
-
|
|
29
|
-
function headers() {
|
|
30
|
-
return {
|
|
31
|
-
Accept: "application/vnd.github+json",
|
|
32
|
-
"User-Agent": "hunch-competitive-watch",
|
|
33
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
34
|
-
...(token && !tokenRejected ? { Authorization: `Bearer ${token}` } : {}),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function github(path) {
|
|
39
|
-
let response = await fetch(`https://api.github.com${path}`, { headers: headers() });
|
|
40
|
-
if (response.status === 401 && token && !tokenRejected) {
|
|
41
|
-
tokenRejected = true;
|
|
42
|
-
process.stderr.write("competitive-watch: GITHUB_TOKEN rejected (401) — continuing unauthenticated; phrase search will be skipped.\n");
|
|
43
|
-
response = await fetch(`https://api.github.com${path}`, { headers: headers() });
|
|
44
|
-
}
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
const detail = (await response.text()).slice(0, 300).replaceAll("\n", " ");
|
|
47
|
-
throw new Error(`GitHub ${response.status} for ${path}: ${detail}`);
|
|
48
|
-
}
|
|
49
|
-
return response.json();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function repoSnapshot(repo) {
|
|
53
|
-
// One vanished/renamed repo is itself a signal worth a row — never a reason
|
|
54
|
-
// to lose the other seven.
|
|
55
|
-
try {
|
|
56
|
-
const data = await github(`/repos/${repo}`);
|
|
57
|
-
return {
|
|
58
|
-
repo,
|
|
59
|
-
created: data.created_at,
|
|
60
|
-
pushed: data.pushed_at,
|
|
61
|
-
stars: data.stargazers_count,
|
|
62
|
-
forks: data.forks_count,
|
|
63
|
-
issues: data.open_issues_count,
|
|
64
|
-
url: data.html_url,
|
|
65
|
-
};
|
|
66
|
-
} catch (error) {
|
|
67
|
-
return { repo, error: error instanceof Error ? error.message : String(error) };
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function phraseSnapshot(phrase) {
|
|
72
|
-
const query = encodeURIComponent(`"${phrase}"`);
|
|
73
|
-
const data = await github(`/search/code?q=${query}&per_page=100`);
|
|
74
|
-
const external = data.items
|
|
75
|
-
.filter((item) => !item.repository.full_name.startsWith("davesheffer/"))
|
|
76
|
-
.map((item) => ({
|
|
77
|
-
repo: item.repository.full_name,
|
|
78
|
-
path: item.path,
|
|
79
|
-
url: item.html_url,
|
|
80
|
-
}));
|
|
81
|
-
return { phrase, total: data.total_count, external };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function render(snapshot, phrases) {
|
|
85
|
-
const lines = [
|
|
86
|
-
`# Competitive watch — ${new Date().toISOString()}`,
|
|
87
|
-
"",
|
|
88
|
-
"## Public repository signals",
|
|
89
|
-
"",
|
|
90
|
-
"| Repository | Created | Last push | Stars | Forks | Open issues |",
|
|
91
|
-
"| --- | --- | --- | ---: | ---: | ---: |",
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
for (const item of snapshot) {
|
|
95
|
-
if (item.error) {
|
|
96
|
-
lines.push(`| ${item.repo} | — | — | — | — | — |`);
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
lines.push(
|
|
100
|
-
`| [${item.repo}](${item.url}) | ${item.created.slice(0, 10)} | ${item.pushed.slice(0, 10)} | ${item.stars} | ${item.forks} | ${item.issues} |`,
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
const failed = snapshot.filter((item) => item.error);
|
|
104
|
-
if (failed.length) {
|
|
105
|
-
lines.push("");
|
|
106
|
-
for (const item of failed) lines.push(`- ⚠ ${item.repo}: ${item.error}`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
lines.push("", "## Distinctive phrase search", "");
|
|
110
|
-
if (!token) {
|
|
111
|
-
lines.push("Skipped: set `GITHUB_TOKEN` to enable authenticated GitHub code search.");
|
|
112
|
-
} else if (tokenRejected) {
|
|
113
|
-
lines.push("Skipped: `GITHUB_TOKEN` was rejected by api.github.com (401) — repo metadata above was collected unauthenticated. Provide a valid token (e.g. `GITHUB_TOKEN=\"$(gh auth token)\"`) to run the phrase-copy check.");
|
|
114
|
-
} else {
|
|
115
|
-
for (const result of phrases) {
|
|
116
|
-
lines.push(`- **${result.phrase}** — ${result.external.length} external indexed match(es)`);
|
|
117
|
-
for (const match of result.external) {
|
|
118
|
-
lines.push(` - [${match.repo} · ${match.path}](${match.url})`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
lines.push(
|
|
124
|
-
"",
|
|
125
|
-
"> Signals are leads, not copying findings. Re-check chronology and substantial similarity before drawing a conclusion.",
|
|
126
|
-
);
|
|
127
|
-
return `${lines.join("\n")}\n`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const snapshot = [];
|
|
132
|
-
// Sequential on purpose: the first call settles token validity before the
|
|
133
|
-
// rest, and unauthenticated rate limits are too small to burst against.
|
|
134
|
-
for (const repo of repos) snapshot.push(await repoSnapshot(repo));
|
|
135
|
-
const phrases = token && !tokenRejected
|
|
136
|
-
? await Promise.all(distinctivePhrases.map(phraseSnapshot))
|
|
137
|
-
: [];
|
|
138
|
-
process.stdout.write(render(snapshot, phrases));
|
|
139
|
-
} catch (error) {
|
|
140
|
-
process.stderr.write(`competitive-watch: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
141
|
-
process.exitCode = 1;
|
|
142
|
-
}
|