@handsupmin/gc-tree 0.7.12 → 0.7.14

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/dist/src/hook.js CHANGED
@@ -6,6 +6,7 @@ import { hookCachePath, } from './paths.js';
6
6
  import { branchScopeStatus, readBranchRepoMap, resolveBranchForRepo } from './repo-map.js';
7
7
  import { resolveContext } from './resolve.js';
8
8
  import { readHead, statusForBranch } from './store.js';
9
+ const SELF_REVIEW_INTERVAL = 5;
9
10
  function normalizeText(value) {
10
11
  return value.trim().replace(/\s+/g, ' ');
11
12
  }
@@ -59,6 +60,15 @@ function buildMatchContext({ gcBranch, currentRepo, repoScopeStatus, query, matc
59
60
  `Read full docs (gctree resolve --id <id>) only if summaries are insufficient. Each doc has a ## Summary section — read that first.`,
60
61
  ].join('\n');
61
62
  }
63
+ function buildSelfReviewAppend(promptCount) {
64
+ return [
65
+ `\n[gc-tree] CONTEXT SELF-REVIEW (every ${SELF_REVIEW_INTERVAL} prompts — this is prompt #${promptCount}):`,
66
+ `At the END of your response to this task, review the current gc-tree global context and compare it against what you have observed or changed in this session.`,
67
+ `If any docs need updating (new conventions discovered, corrected info, new repos or workflows, outdated content), propose the specific changes to the user and ask whether to apply them.`,
68
+ `Use \`/gc-update-global-context\` (Claude Code) or \`gctree update-global-context\` (CLI) to apply.`,
69
+ `If nothing meaningful changed, skip silently — do not mention this review to the user.`,
70
+ ].join('\n');
71
+ }
62
72
  async function readHookCache(home, sessionId) {
63
73
  try {
64
74
  const raw = await readFile(hookCachePath(home, sessionId), 'utf8');
@@ -87,6 +97,7 @@ function nextCacheState(previous, { sessionId, gcBranch, currentRepo, repoScopeS
87
97
  branch_empty: changed ? false : previous.branch_empty,
88
98
  branch_excluded: changed ? false : previous.branch_excluded,
89
99
  no_match_signatures: changed ? [] : previous.no_match_signatures,
100
+ prompt_count: (previous?.prompt_count ?? 0) + 1,
90
101
  updated_at: new Date().toISOString(),
91
102
  };
92
103
  }
@@ -126,84 +137,43 @@ export async function dispatchGcTreeHook({ event, home, payload, }) {
126
137
  currentRepo,
127
138
  repoScopeStatus,
128
139
  });
140
+ let additionalContext;
129
141
  if (repoScopeStatus === 'excluded') {
130
142
  cache.branch_excluded = true;
131
- cache.updated_at = new Date().toISOString();
132
- await writeHookCache(home, cache);
133
- return {
134
- hookSpecificOutput: {
135
- hookEventName: event,
136
- additionalContext: buildExcludedContext({
137
- gcBranch,
138
- currentRepo,
139
- cached: previousCache?.branch_excluded === true,
140
- }),
141
- },
142
- };
143
+ additionalContext = buildExcludedContext({
144
+ gcBranch,
145
+ currentRepo,
146
+ cached: previousCache?.branch_excluded === true,
147
+ });
143
148
  }
144
- if (branchStatus.doc_count === 0) {
149
+ else if (branchStatus.doc_count === 0) {
145
150
  const wasCached = cache.branch_empty;
146
151
  cache.branch_empty = true;
147
- cache.updated_at = new Date().toISOString();
148
- await writeHookCache(home, cache);
149
- return {
150
- hookSpecificOutput: {
151
- hookEventName: event,
152
- additionalContext: buildEmptyBranchContext({
153
- gcBranch,
154
- currentRepo,
155
- cached: wasCached,
156
- }),
157
- },
158
- };
152
+ additionalContext = buildEmptyBranchContext({ gcBranch, currentRepo, cached: wasCached });
159
153
  }
160
- const query = currentRepo ? `${currentRepo} ${prompt}` : prompt;
161
- const signature = hashQuery(query);
162
- if (cache.no_match_signatures.includes(signature)) {
163
- cache.updated_at = new Date().toISOString();
164
- await writeHookCache(home, cache);
165
- return {
166
- hookSpecificOutput: {
167
- hookEventName: event,
168
- additionalContext: buildNoMatchContext({
169
- gcBranch,
170
- currentRepo,
171
- query,
172
- cached: true,
173
- }),
174
- },
175
- };
154
+ else {
155
+ const query = currentRepo ? `${currentRepo} ${prompt}` : prompt;
156
+ const signature = hashQuery(query);
157
+ if (cache.no_match_signatures.includes(signature)) {
158
+ additionalContext = buildNoMatchContext({ gcBranch, currentRepo, query, cached: true });
159
+ }
160
+ else {
161
+ const result = await resolveContext({ home, branch: gcBranch, query });
162
+ if (result.matches.length === 0) {
163
+ cache.no_match_signatures = [...new Set([...cache.no_match_signatures, signature])];
164
+ additionalContext = buildNoMatchContext({ gcBranch, currentRepo, query, cached: false });
165
+ }
166
+ else {
167
+ cache.no_match_signatures = cache.no_match_signatures.filter((entry) => entry !== signature);
168
+ additionalContext = buildMatchContext({ gcBranch, currentRepo, repoScopeStatus, query, matches: result.matches });
169
+ }
170
+ }
171
+ if (cache.prompt_count > 0 && cache.prompt_count % SELF_REVIEW_INTERVAL === 0) {
172
+ additionalContext += buildSelfReviewAppend(cache.prompt_count);
173
+ cache.prompt_count = 0;
174
+ }
176
175
  }
177
- const result = await resolveContext({ home, branch: gcBranch, query });
178
- if (result.matches.length === 0) {
179
- cache.no_match_signatures = [...new Set([...cache.no_match_signatures, signature])];
180
- cache.updated_at = new Date().toISOString();
181
- await writeHookCache(home, cache);
182
- return {
183
- hookSpecificOutput: {
184
- hookEventName: event,
185
- additionalContext: buildNoMatchContext({
186
- gcBranch,
187
- currentRepo,
188
- query,
189
- cached: false,
190
- }),
191
- },
192
- };
193
- }
194
- cache.no_match_signatures = cache.no_match_signatures.filter((entry) => entry !== signature);
195
176
  cache.updated_at = new Date().toISOString();
196
177
  await writeHookCache(home, cache);
197
- return {
198
- hookSpecificOutput: {
199
- hookEventName: event,
200
- additionalContext: buildMatchContext({
201
- gcBranch,
202
- currentRepo,
203
- repoScopeStatus,
204
- query,
205
- matches: result.matches,
206
- }),
207
- },
208
- };
178
+ return { hookSpecificOutput: { hookEventName: event, additionalContext } };
209
179
  }
@@ -71,9 +71,22 @@ export function ensureSummary(summary) {
71
71
  }
72
72
  return trimmed;
73
73
  }
74
+ function normalizeBody(raw, title) {
75
+ let s = raw.trim();
76
+ // Strip leading "# <title>" line (already rendered at top level)
77
+ const titleLine = `# ${title.trim()}`;
78
+ if (s.startsWith(titleLine))
79
+ s = s.slice(titleLine.length).trimStart();
80
+ // Strip leading "## Summary" block (already rendered at top level)
81
+ if (s.startsWith('## Summary')) {
82
+ const next = s.match(/\n(?=## )/);
83
+ s = next ? s.slice(next.index).trimStart() : '';
84
+ }
85
+ return s;
86
+ }
74
87
  export function renderDocMarkdown(doc) {
75
88
  const summary = ensureSummary(doc.summary);
76
- const body = String(doc.body || '').trim();
89
+ const body = normalizeBody(String(doc.body || ''), doc.title);
77
90
  const normalizedIndexEntries = [...new Set([
78
91
  ...(doc.indexLabel?.trim() ? [doc.indexLabel.trim()] : []),
79
92
  ...(doc.indexEntries || []).map((entry) => String(entry || '').trim()).filter(Boolean),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handsupmin/gc-tree",
3
- "version": "0.7.12",
3
+ "version": "0.7.14",
4
4
  "description": "Global Context Tree, a lightweight branch-aware global context orchestrator for AI coding tools",
5
5
  "type": "module",
6
6
  "private": false,