@davesheffer/hunch 1.47.2 → 1.48.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 +105 -201
- package/NOTICE +10 -3
- package/README.md +8 -6
- package/dist/cli/index.js +14 -5
- package/dist/core/format.d.ts +3 -1
- package/dist/core/format.js +14 -2
- package/dist/core/reviewMemory.d.ts +1 -1
- package/dist/extractors/parse.js +20 -30
- package/dist/integrations/hooks.js +10 -8
- package/dist/store/hunchStore.d.ts +10 -6
- package/dist/store/hunchStore.js +96 -27
- package/dist/synthesis/prContext.d.ts +75 -0
- package/dist/synthesis/prContext.js +265 -0
- package/dist/synthesis/provider.d.ts +38 -0
- package/dist/synthesis/provider.js +110 -13
- package/dist/synthesis/synthesize.d.ts +14 -0
- package/dist/synthesis/synthesize.js +46 -6
- package/package.json +2 -2
- package/server.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,201 +1,105 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
-
distribution, then any Derivative Works that You distribute must
|
|
108
|
-
include a readable copy of the attribution notices contained
|
|
109
|
-
within such NOTICE file, excluding those notices that do not
|
|
110
|
-
pertain to any part of the Derivative Works, in at least one
|
|
111
|
-
of the following places: within a NOTICE text file distributed
|
|
112
|
-
as part of the Derivative Works; within the Source form or
|
|
113
|
-
documentation, if provided along with the Derivative Works; or,
|
|
114
|
-
within a display generated by the Derivative Works, if and
|
|
115
|
-
wherever such third-party notices normally appear. The contents
|
|
116
|
-
of the NOTICE file are for informational purposes only and
|
|
117
|
-
do not modify the License. You may add Your own attribution
|
|
118
|
-
notices within Derivative Works that You distribute, alongside
|
|
119
|
-
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
-
that such additional attribution notices cannot be construed
|
|
121
|
-
as modifying the License.
|
|
122
|
-
|
|
123
|
-
You may add Your own copyright statement to Your modifications and
|
|
124
|
-
may provide additional or different license terms and conditions
|
|
125
|
-
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
-
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
-
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
-
the conditions stated in this License.
|
|
129
|
-
|
|
130
|
-
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
-
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
-
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
-
this License, without any additional terms or conditions.
|
|
134
|
-
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
-
the terms of any separate license agreement you may have executed
|
|
136
|
-
with Licensor regarding such Contributions.
|
|
137
|
-
|
|
138
|
-
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
-
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
-
except as required for reasonable and customary use in describing the
|
|
141
|
-
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
-
|
|
143
|
-
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
-
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
-
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
-
implied, including, without limitation, any warranties or conditions
|
|
148
|
-
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
-
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
-
appropriateness of using or redistributing the Work and assume any
|
|
151
|
-
risks associated with Your exercise of permissions under this License.
|
|
152
|
-
|
|
153
|
-
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
-
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
-
unless required by applicable law (such as deliberate and grossly
|
|
156
|
-
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
-
liable to You for damages, including any direct, indirect, special,
|
|
158
|
-
incidental, or consequential damages of any character arising as a
|
|
159
|
-
result of this License or out of the use or inability to use the
|
|
160
|
-
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
-
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
-
other commercial damages or losses), even if such Contributor
|
|
163
|
-
has been advised of the possibility of such damages.
|
|
164
|
-
|
|
165
|
-
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
-
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
-
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
-
or other liability obligations and/or rights consistent with this
|
|
169
|
-
License. However, in accepting such obligations, You may act only
|
|
170
|
-
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
-
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
-
defend, and hold each Contributor harmless for any liability
|
|
173
|
-
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
-
of your accepting any such warranty or additional liability.
|
|
175
|
-
|
|
176
|
-
END OF TERMS AND CONDITIONS
|
|
177
|
-
|
|
178
|
-
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
-
|
|
180
|
-
To apply the Apache License to your work, attach the following
|
|
181
|
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
-
replaced with your own identifying information. (Don't include
|
|
183
|
-
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
-
comment syntax for the file format. We also recommend that a
|
|
185
|
-
file or class name and description of purpose be included on the
|
|
186
|
-
same "printed page" as the copyright notice for easier
|
|
187
|
-
identification within third-party archives.
|
|
188
|
-
|
|
189
|
-
Copyright 2026 Dave Sheffer
|
|
190
|
-
|
|
191
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
-
you may not use this file except in compliance with the License.
|
|
193
|
-
You may obtain a copy of the License at
|
|
194
|
-
|
|
195
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
-
|
|
197
|
-
Unless required by applicable law or agreed to in writing, software
|
|
198
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
-
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-ALv2
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Dave Sheffer
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
package/NOTICE
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
Hunch — Engineering Memory and Change Gate
|
|
2
2
|
Copyright 2026 Dave Sheffer
|
|
3
3
|
|
|
4
|
-
This product is licensed under the
|
|
5
|
-
See the LICENSE file for the full
|
|
6
|
-
|
|
4
|
+
This product is licensed under the Functional Source License, Version 1.1,
|
|
5
|
+
ALv2 Future License (FSL-1.1-ALv2). See the LICENSE file for the full text,
|
|
6
|
+
or https://fsl.software. Each version becomes available under the Apache
|
|
7
|
+
License, Version 2.0 on the second anniversary of its release.
|
|
8
|
+
|
|
9
|
+
Versions released before the switch to FSL-1.1-ALv2 remain available under
|
|
10
|
+
the license they shipped with (Apache-2.0 from v0.15.2, MIT before that).
|
|
11
|
+
|
|
12
|
+
The Python client in clients/python is licensed separately under the Apache
|
|
13
|
+
License, Version 2.0; see clients/python/LICENSE.
|
package/README.md
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@davesheffer/hunch)
|
|
6
6
|
[](https://github.com/davesheffer/hunch)
|
|
7
|
-
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
[](https://www.youtube.com/watch?v=m2PM6pV7D9Y)
|
|
10
|
+
|
|
11
|
+
▶ **[Watch Hunch Memory in 2 minutes](https://www.youtube.com/watch?v=m2PM6pV7D9Y)**, then the [7-episode series](https://www.youtube.com/playlist?list=PLIXqtdE0LcNI) on setup, hooks, memory capture, delivery, rules, drift and teams.
|
|
12
|
+
|
|
13
|
+

|
|
8
14
|
|
|
9
15
|
A new agent session should not mean explaining the project all over again. A second agent should be able to check a recorded decision, see the evidence behind completed work, and find an outstanding commitment.
|
|
10
16
|
|
|
@@ -12,10 +18,6 @@ Hunch keeps that record in Git and makes the relevant parts available to your ag
|
|
|
12
18
|
|
|
13
19
|
The goal is simple: agents working from the same maintained record, with sources they can inspect. Hunch supplies memory and checks; the assistant still does the work.
|
|
14
20
|
|
|
15
|
-
[](https://www.youtube.com/watch?v=m2PM6pV7D9Y)
|
|
16
|
-
|
|
17
|
-
▶ **[Watch Hunch Memory in 2 minutes](https://www.youtube.com/watch?v=m2PM6pV7D9Y)**, then the [7-episode series](https://www.youtube.com/playlist?list=PLIXqtdE0LcNI) on setup, hooks, memory capture, delivery, rules, drift and teams.
|
|
18
|
-
|
|
19
21
|
## Start with your coding assistant
|
|
20
22
|
|
|
21
23
|
Requires **Node 22.13+** and a Git repository.
|
|
@@ -196,4 +198,4 @@ Profiles retain their revision, sources, confidence, and freshness.
|
|
|
196
198
|
- [VS Code extension](vscode-extension/README.md)
|
|
197
199
|
- [Contributing](CONTRIBUTING.md)
|
|
198
200
|
|
|
199
|
-
Apache-2.0
|
|
201
|
+
[FSL-1.1-ALv2](LICENSE) (Functional Source License). Use it, modify it, and run it for your own work, internal or commercial; you can't make it available to others in a commercial product or service that competes with Hunch or with services built on it (see Permitted Purpose in [LICENSE](LICENSE)). Each release converts to Apache-2.0 two years after it ships. Releases before this change keep the license they shipped with.
|
package/dist/cli/index.js
CHANGED
|
@@ -857,7 +857,7 @@ program
|
|
|
857
857
|
if (ctxWarning !== null)
|
|
858
858
|
console.log(ctxWarning);
|
|
859
859
|
}
|
|
860
|
-
let written = 0, skipped = 0, llm = 0, heuristic = 0;
|
|
860
|
+
let written = 0, skipped = 0, llm = 0, heuristic = 0, fellBack = 0;
|
|
861
861
|
const home = store.captureHome(false);
|
|
862
862
|
// The per-commit cost is the Claude synthesis spawn; run several at once. Safe:
|
|
863
863
|
// each commit drafts independently and writes its OWN decision file atomically,
|
|
@@ -869,11 +869,17 @@ program
|
|
|
869
869
|
if (r.status === "written") {
|
|
870
870
|
written++;
|
|
871
871
|
// Any non-deterministic provider (claude/codex/cursor/ensemble) is an LLM draft.
|
|
872
|
-
|
|
872
|
+
// A provider failure that fell back is counted apart from the commits the
|
|
873
|
+
// significance gate sent to the heuristic on purpose (issue #561).
|
|
874
|
+
if (r.fallbackReason)
|
|
875
|
+
fellBack++;
|
|
876
|
+
else if (r.provider && r.provider !== "deterministic")
|
|
873
877
|
llm++;
|
|
874
878
|
else
|
|
875
879
|
heuristic++;
|
|
876
|
-
process.stdout.write(` ✓ ${sha.slice(0, 8)} ${r.decision?.title.slice(0, 64) ?? ""}\n`);
|
|
880
|
+
process.stdout.write(` ${r.fallbackReason ? "⚠" : "✓"} ${sha.slice(0, 8)} ${r.decision?.title.slice(0, 64) ?? ""}\n`);
|
|
881
|
+
if (r.fallbackReason)
|
|
882
|
+
process.stdout.write(` heuristic draft instead: ${r.fallbackReason}\n`);
|
|
877
883
|
}
|
|
878
884
|
else
|
|
879
885
|
skipped++;
|
|
@@ -885,7 +891,8 @@ program
|
|
|
885
891
|
pumpMemoryHome(store, root, home, `hunch: backfill ${written} decision(s)`);
|
|
886
892
|
// Honest tally of where the tokens went: trivial commits are seeded by the
|
|
887
893
|
// free deterministic heuristic, only substantive ones spend the LLM.
|
|
888
|
-
|
|
894
|
+
const fellBackNote = fellBack ? `, ${fellBack} heuristic after the LLM failed` : "";
|
|
895
|
+
console.log(`Done: ${written} decision(s) seeded (${llm} via LLM, ${heuristic} heuristic${fellBackNote}), ${skipped} skipped (trivial/not substantive/already-captured).`);
|
|
889
896
|
store.close();
|
|
890
897
|
});
|
|
891
898
|
// ---- sync (post-commit hook) ----------------------------------------------
|
|
@@ -948,6 +955,8 @@ program
|
|
|
948
955
|
store.reindex();
|
|
949
956
|
if (!opts.quiet)
|
|
950
957
|
console.log(`✓ captured decision ${r.decision?.id} via ${r.provider}: "${r.decision?.title}"`);
|
|
958
|
+
if (!opts.quiet && r.fallbackReason)
|
|
959
|
+
console.log(` ⚠ heuristic draft instead: ${r.fallbackReason}`);
|
|
951
960
|
}
|
|
952
961
|
else if (!opts.quiet) {
|
|
953
962
|
console.log(`· skipped: ${r.reason}`);
|
|
@@ -2084,7 +2093,7 @@ program
|
|
|
2084
2093
|
else {
|
|
2085
2094
|
console.log(`Top matches for "${q}"${how}:\n`);
|
|
2086
2095
|
for (const h of hits)
|
|
2087
|
-
console.log(formatSearchHit(h, isStateKind(h.kind) ? store.resolve(h.ref)?.record : undefined));
|
|
2096
|
+
console.log(formatSearchHit(h, isStateKind(h.kind) || h.kind === "decisions" ? store.resolve(h.ref)?.record : undefined));
|
|
2088
2097
|
}
|
|
2089
2098
|
store.close();
|
|
2090
2099
|
});
|
package/dist/core/format.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export declare function formatContext(ctx: AssembledContext, options?: DeliveryO
|
|
|
7
7
|
/** One search hit as `hunch query` / hunch_query print it (headline + indented detail line).
|
|
8
8
|
* Graph records keep their `[kind] id — title` shape; a nuryel.state/1 hit renders through
|
|
9
9
|
* renderStateLine (`[commitment/in_force] customer:Site:7 — "send report" due … (owner …)`)
|
|
10
|
-
* with the record id on the detail line, so both readers say the same thing.
|
|
10
|
+
* with the record id on the detail line, so both readers say the same thing. A decision that
|
|
11
|
+
* is no longer accepted says so in its tag (`[decisions · superseded → dec_new]`), so a
|
|
12
|
+
* reader never takes a history hit for the current rule. */
|
|
11
13
|
export declare function formatSearchHit(hit: SearchHit, record: unknown): string;
|
|
12
14
|
/** Render a StructureView as a compact orientation brief (hunch_structure). */
|
|
13
15
|
export declare function formatStructure(v: StructureView): string;
|
package/dist/core/format.js
CHANGED
|
@@ -6,12 +6,24 @@ export function formatContext(ctx, options = {}) {
|
|
|
6
6
|
/** One search hit as `hunch query` / hunch_query print it (headline + indented detail line).
|
|
7
7
|
* Graph records keep their `[kind] id — title` shape; a nuryel.state/1 hit renders through
|
|
8
8
|
* renderStateLine (`[commitment/in_force] customer:Site:7 — "send report" due … (owner …)`)
|
|
9
|
-
* with the record id on the detail line, so both readers say the same thing.
|
|
9
|
+
* with the record id on the detail line, so both readers say the same thing. A decision that
|
|
10
|
+
* is no longer accepted says so in its tag (`[decisions · superseded → dec_new]`), so a
|
|
11
|
+
* reader never takes a history hit for the current rule. */
|
|
10
12
|
export function formatSearchHit(hit, record) {
|
|
11
13
|
if (isStateKind(hit.kind) && record) {
|
|
12
14
|
return `• ${renderStateLine(hit.kind, record)}\n ${hit.ref}`;
|
|
13
15
|
}
|
|
14
|
-
return `• [${hit.kind}] ${hit.ref} — ${hit.title}\n ${hit.snippet}`;
|
|
16
|
+
return `• [${hit.kind}${decisionStatusTag(hit.kind, record)}] ${hit.ref} — ${hit.title}\n ${hit.snippet}`;
|
|
17
|
+
}
|
|
18
|
+
function decisionStatusTag(kind, record) {
|
|
19
|
+
if (kind !== "decisions" || !record)
|
|
20
|
+
return "";
|
|
21
|
+
const { status, superseded_by, valid_to } = record;
|
|
22
|
+
if (superseded_by)
|
|
23
|
+
return ` · superseded → ${superseded_by}`;
|
|
24
|
+
if (status && status !== "accepted")
|
|
25
|
+
return ` · ${status}`;
|
|
26
|
+
return valid_to ? ` · closed ${valid_to.slice(0, 10)}` : "";
|
|
15
27
|
}
|
|
16
28
|
/** Render a StructureView as a compact orientation brief (hunch_structure). */
|
|
17
29
|
export function formatStructure(v) {
|
package/dist/extractors/parse.js
CHANGED
|
@@ -242,6 +242,22 @@ function ascendToDef(node, defNodeTypes) {
|
|
|
242
242
|
}
|
|
243
243
|
return null;
|
|
244
244
|
}
|
|
245
|
+
/** Index in `symbols` of the narrowest symbol whose [startByte, endByte) range
|
|
246
|
+
* holds `at`; on equal spans the earlier symbol wins. -1 when nothing encloses it. */
|
|
247
|
+
function innermostSymbolAt(symbols, at) {
|
|
248
|
+
let found = -1;
|
|
249
|
+
let narrowest = Infinity;
|
|
250
|
+
symbols.forEach((sym, i) => {
|
|
251
|
+
if (at < sym.startByte || at >= sym.endByte)
|
|
252
|
+
return;
|
|
253
|
+
const span = sym.endByte - sym.startByte;
|
|
254
|
+
if (span < narrowest) {
|
|
255
|
+
narrowest = span;
|
|
256
|
+
found = i;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return found;
|
|
260
|
+
}
|
|
245
261
|
/** Map each call site to the innermost symbol whose byte-range contains it.
|
|
246
262
|
* The result is keyed by the symbol's INDEX in `parsed.symbols`, not its start
|
|
247
263
|
* byte: two distinct symbols can share a start byte (a synthetic whole-file root
|
|
@@ -252,17 +268,8 @@ function ascendToDef(node, defNodeTypes) {
|
|
|
252
268
|
export function attributeCalls(parsed) {
|
|
253
269
|
const out = new Map();
|
|
254
270
|
for (const call of parsed.calls) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
for (let i = 0; i < parsed.symbols.length; i++) {
|
|
258
|
-
const s = parsed.symbols[i];
|
|
259
|
-
if (call.atByte >= s.startByte && call.atByte < s.endByte) {
|
|
260
|
-
if (!best || s.endByte - s.startByte < best.endByte - best.startByte) {
|
|
261
|
-
best = s;
|
|
262
|
-
bestIndex = i;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
271
|
+
const bestIndex = innermostSymbolAt(parsed.symbols, call.atByte);
|
|
272
|
+
const best = bestIndex >= 0 ? parsed.symbols[bestIndex] : undefined;
|
|
266
273
|
if (best && best.name !== call.callee) {
|
|
267
274
|
let m = out.get(bestIndex);
|
|
268
275
|
if (!m) {
|
|
@@ -305,15 +312,7 @@ export function attributeQualifiedCalls(parsed) {
|
|
|
305
312
|
}
|
|
306
313
|
if (!target)
|
|
307
314
|
continue;
|
|
308
|
-
|
|
309
|
-
let bestIndex = -1;
|
|
310
|
-
for (let i = 0; i < parsed.symbols.length; i++) {
|
|
311
|
-
const s = parsed.symbols[i];
|
|
312
|
-
if (call.atByte >= s.startByte && call.atByte < s.endByte && (!best || s.endByte - s.startByte < best.endByte - best.startByte)) {
|
|
313
|
-
best = s;
|
|
314
|
-
bestIndex = i;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
315
|
+
const bestIndex = innermostSymbolAt(parsed.symbols, call.atByte);
|
|
317
316
|
if (bestIndex < 0)
|
|
318
317
|
continue;
|
|
319
318
|
const list = out.get(bestIndex) ?? [];
|
|
@@ -329,16 +328,7 @@ export function attributeQualifiedCalls(parsed) {
|
|
|
329
328
|
export function attributeRelations(parsed) {
|
|
330
329
|
const out = new Map();
|
|
331
330
|
for (const relation of parsed.relations) {
|
|
332
|
-
|
|
333
|
-
let bestIndex = -1;
|
|
334
|
-
for (let index = 0; index < parsed.symbols.length; index++) {
|
|
335
|
-
const symbol = parsed.symbols[index];
|
|
336
|
-
if (relation.atByte >= symbol.startByte && relation.atByte < symbol.endByte
|
|
337
|
-
&& (!best || symbol.endByte - symbol.startByte < best.endByte - best.startByte)) {
|
|
338
|
-
best = symbol;
|
|
339
|
-
bestIndex = index;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
331
|
+
const bestIndex = innermostSymbolAt(parsed.symbols, relation.atByte);
|
|
342
332
|
if (bestIndex < 0)
|
|
343
333
|
continue;
|
|
344
334
|
const list = out.get(bestIndex) ?? [];
|
|
@@ -609,14 +609,16 @@ function sharedInvocationFor(root, mark, invocation, own) {
|
|
|
609
609
|
}
|
|
610
610
|
return null;
|
|
611
611
|
}
|
|
612
|
-
/**
|
|
613
|
-
* hook
|
|
614
|
-
* invocation
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
612
|
+
/** The one idempotent install path every Hunch git hook goes through: create the
|
|
613
|
+
* hook file when there is none; rewrite our own managed block in place when its
|
|
614
|
+
* invocation moved; otherwise append the block after whatever non-Hunch body the
|
|
615
|
+
* file already holds, never clobbering it. Each installer used to carry its own
|
|
616
|
+
* copy of this, and the copies drifted (one forgot to chmod the file on its
|
|
617
|
+
* update path), hence a single shared implementation.
|
|
618
|
+
*
|
|
619
|
+
* When a hook manager owns the file, or the block would land after an `exec` /
|
|
620
|
+
* `exit` that stops it from ever running, nothing is written: the result
|
|
621
|
+
* carries the snippet for the user to add by hand instead (issue #311). */
|
|
620
622
|
function installManagedBlock(root, hookName, mark, end, build, invocation) {
|
|
621
623
|
// A linked worktree whose branch predates the opt-out marker shares the main
|
|
622
624
|
// checkout's hooks dir: never re-hook a repository `hunch uninstall` opted out (#488).
|
|
@@ -281,10 +281,11 @@ export declare class HunchStore {
|
|
|
281
281
|
total: number;
|
|
282
282
|
}>;
|
|
283
283
|
/** Post-fusion rerank by graph PRIORS (dec_25e277f479): relevance ordering, not
|
|
284
|
-
* just reachability. Trust weight w =
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
284
|
+
* just reachability. Trust weight w = provenance × recency: provenance 1.0 / 0.9 /
|
|
285
|
+
* 0.85 / 0.75 for human_confirmed / agent_recorded / llm_draft / extracted-inferred,
|
|
286
|
+
* recency 0.7 + 0.3·½^(age/90d), and proven anchored-file staleness 0.8. Liveness
|
|
287
|
+
* (superseded/retired/rejected) is a separate DEAD_PRIOR_SHIFT, and a superseded
|
|
288
|
+
* decision always lands just below its live successor. Every factor is ranking-only.
|
|
288
289
|
* Runbook trigger phrases matching the query boost ×1.5 (exact intent beats
|
|
289
290
|
* keyword luck). Structural refs (symbols/components/edges) stay neutral.
|
|
290
291
|
*
|
|
@@ -302,10 +303,13 @@ export declare class HunchStore {
|
|
|
302
303
|
* they were the #1 fused hit, and 88% from fused rank 8. Both retrieval layers
|
|
303
304
|
* ranked the record first and the rerank alone threw it away.
|
|
304
305
|
*
|
|
305
|
-
* A shift of at most ±MAX_PRIOR_SHIFT positions
|
|
306
|
+
* A shift of at most ±MAX_PRIOR_SHIFT positions (+DEAD_PRIOR_SHIFT for history)
|
|
307
|
+
* restores the intended semantics:
|
|
306
308
|
* a stale or low-provenance record visibly dims, an exact runbook-trigger match
|
|
307
309
|
* visibly promotes, and neither can leapfrog the whole pool. Same measurement
|
|
308
|
-
* after: 0% evicted from fused ranks 0–8
|
|
310
|
+
* after: 0% of live records evicted from fused ranks 0–8 (a dead record is meant to
|
|
311
|
+
* sink; only from fused #0/#1 is it guaranteed to stay on an 8-hit page).
|
|
312
|
+
* Deterministic; ties keep fused order. */
|
|
309
313
|
private rerankByPriors;
|
|
310
314
|
/**
|
|
311
315
|
* Score only freshness that the existing graph clocks can prove: an anchored path changed after
|