@esneiderbravo/speclaw 0.3.11 → 0.3.13

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.
Files changed (29) hide show
  1. package/dist/cli/commands/lawbook.js +84 -6
  2. package/dist/cli/commands/quick.js +35 -0
  3. package/dist/cli/commands/update.js +18 -0
  4. package/dist/cli/index.js +17 -1
  5. package/dist/modules/foundation/doctor.js +94 -0
  6. package/dist/modules/lawbook/assets/commands/archive.md +5 -6
  7. package/dist/modules/lawbook/assets/commands/draft.md +6 -7
  8. package/dist/modules/lawbook/assets/commands/investigate.md +7 -0
  9. package/dist/modules/lawbook/assets/commands/quick.md +14 -0
  10. package/dist/modules/lawbook/assets/rules/spec-reports-disciplines.md +8 -0
  11. package/dist/modules/lawbook/assets/skills/archive/steps/03-validate-and-sync.md +4 -3
  12. package/dist/modules/lawbook/assets/skills/draft/SKILL.md +1 -1
  13. package/dist/modules/lawbook/assets/skills/draft/steps/02-understand.md +3 -0
  14. package/dist/modules/lawbook/assets/skills/draft/steps/04-write-artifacts.md +29 -25
  15. package/dist/modules/lawbook/assets/skills/investigate/SKILL.md +10 -0
  16. package/dist/modules/lawbook/assets/skills/investigate/steps/01-investigate.md +7 -0
  17. package/dist/modules/lawbook/assets/skills/investigate/steps/02-hand-off.md +6 -0
  18. package/dist/modules/lawbook/assets/skills/quick/SKILL.md +11 -0
  19. package/dist/modules/lawbook/assets/skills/quick/steps/01-scaffold.md +6 -0
  20. package/dist/modules/lawbook/assets/skills/quick/steps/02-implement.md +7 -0
  21. package/dist/modules/lawbook/bugfix.js +195 -0
  22. package/dist/modules/lawbook/engine.js +178 -55
  23. package/dist/modules/lawbook/investigate.js +358 -0
  24. package/dist/modules/lawbook/levels.js +468 -0
  25. package/dist/modules/lawbook/quick.js +86 -0
  26. package/dist/modules/lawbook/register.js +18 -0
  27. package/dist/modules/lawbook/stack-parse.js +135 -0
  28. package/dist/shared/exposure.js +2 -0
  29. package/package.json +1 -1
@@ -0,0 +1,135 @@
1
+ import path from "node:path";
2
+ const V8_NAMED = /^\s*at\s+(?:async\s+)?(?:(.+?)\s+\()?([^\s()]+):(\d+):(\d+)\)?\s*$/;
3
+ const V8_ANON = /^\s*at\s+([^\s()]+):(\d+):(\d+)\s*$/;
4
+ const PY_FRAME = /^\s*File\s+"([^"]+)",\s*line\s+(\d+)(?:,\s*in\s+(.+))?\s*$/;
5
+ function isExternal(rawPath) {
6
+ const n = rawPath.replace(/\\/g, "/");
7
+ return (n.includes("node_modules/") ||
8
+ n.includes("node:internal") ||
9
+ n.startsWith("node:") ||
10
+ n.includes("/lib/python") ||
11
+ n.includes("site-packages/"));
12
+ }
13
+ /**
14
+ * Map `dist/foo.js` → `src/foo.ts` when the basename matches (no silent guess
15
+ * beyond that convention).
16
+ */
17
+ export function mapDistToSrc(rel) {
18
+ const n = rel.replace(/\\/g, "/");
19
+ if (!n.startsWith("dist/"))
20
+ return n;
21
+ const base = path.basename(n, path.extname(n));
22
+ return `src/${base}.ts`;
23
+ }
24
+ /**
25
+ * Normalize an absolute or relative trace path to a project-relative path.
26
+ *
27
+ * @param projectPath - Project root used to strip prefixes.
28
+ * @param rawPath - Path as it appears in the trace.
29
+ */
30
+ export function normalizeTracePath(projectPath, rawPath) {
31
+ let p = rawPath.replace(/\\/g, "/");
32
+ const root = projectPath.replace(/\\/g, "/");
33
+ if (p.startsWith(root + "/"))
34
+ p = p.slice(root.length + 1);
35
+ if (p.startsWith("file://")) {
36
+ try {
37
+ p = decodeURIComponent(new URL(p).pathname);
38
+ if (p.startsWith(root + "/"))
39
+ p = p.slice(root.length + 1);
40
+ }
41
+ catch {
42
+ /* keep raw */
43
+ }
44
+ }
45
+ p = p.replace(/^\.\//, "");
46
+ p = mapDistToSrc(p);
47
+ return p;
48
+ }
49
+ function parseV8Line(line, projectPath) {
50
+ const named = V8_NAMED.exec(line);
51
+ if (named) {
52
+ const rawPath = named[2];
53
+ if (isExternal(rawPath))
54
+ return { raw: line.trim(), reason: "external" };
55
+ const fn = named[1]?.trim() ?? "";
56
+ const file = normalizeTracePath(projectPath, rawPath);
57
+ return { fn, file, line: Number(named[3]), rawPath };
58
+ }
59
+ const anon = V8_ANON.exec(line);
60
+ if (anon) {
61
+ const rawPath = anon[1];
62
+ if (isExternal(rawPath))
63
+ return { raw: line.trim(), reason: "external" };
64
+ return {
65
+ fn: "",
66
+ file: normalizeTracePath(projectPath, rawPath),
67
+ line: Number(anon[2]),
68
+ rawPath,
69
+ };
70
+ }
71
+ return null;
72
+ }
73
+ function parsePythonLines(lines, projectPath) {
74
+ const pyFrames = [];
75
+ const unresolved = [];
76
+ for (const line of lines) {
77
+ const m = PY_FRAME.exec(line);
78
+ if (!m)
79
+ continue;
80
+ const rawPath = m[1];
81
+ if (isExternal(rawPath)) {
82
+ unresolved.push({ raw: line.trim(), reason: "external" });
83
+ continue;
84
+ }
85
+ pyFrames.push({
86
+ fn: m[3]?.trim() ?? "",
87
+ file: normalizeTracePath(projectPath, rawPath),
88
+ line: Number(m[2]),
89
+ rawPath,
90
+ });
91
+ }
92
+ // Python traces list shallow→deep; invert to deepest-first like V8.
93
+ pyFrames.reverse();
94
+ return { frames: pyFrames, unresolved, format: "python" };
95
+ }
96
+ /**
97
+ * Parse a V8 (Node) or Python stack trace into own-project frames.
98
+ *
99
+ * @param projectPath - Project root for path normalization.
100
+ * @param stackTrace - Raw stack trace text.
101
+ */
102
+ export function parseStackTrace(projectPath, stackTrace) {
103
+ const lines = stackTrace.split("\n");
104
+ const pyProbe = lines.some((l) => PY_FRAME.test(l));
105
+ if (pyProbe)
106
+ return parsePythonLines(lines, projectPath);
107
+ const frames = [];
108
+ const unresolved = [];
109
+ let sawV8 = false;
110
+ for (const line of lines) {
111
+ const parsed = parseV8Line(line, projectPath);
112
+ if (!parsed)
113
+ continue;
114
+ sawV8 = true;
115
+ if ("reason" in parsed)
116
+ unresolved.push(parsed);
117
+ else
118
+ frames.push(parsed);
119
+ }
120
+ if (!sawV8 && stackTrace.trim()) {
121
+ for (const line of lines) {
122
+ if (line.trim())
123
+ unresolved.push({ raw: line.trim(), reason: "unparseable" });
124
+ }
125
+ return { frames: [], unresolved, format: "unknown" };
126
+ }
127
+ return { frames, unresolved, format: "v8" };
128
+ }
129
+ /** Extract a simple method name from `Class.method` frames. */
130
+ export function frameSymbolName(frame) {
131
+ if (!frame.fn)
132
+ return "";
133
+ const dot = frame.fn.lastIndexOf(".");
134
+ return dot >= 0 ? frame.fn.slice(dot + 1) : frame.fn;
135
+ }
@@ -19,6 +19,8 @@ export const MINIMAL_OMIT = new Set([
19
19
  "lawbook_init",
20
20
  "lawbook_archive",
21
21
  "lawbook_list",
22
+ "lawbook_level",
23
+ "lawbook_investigate",
22
24
  "init_project",
23
25
  "scaffold",
24
26
  "configure_agent",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esneiderbravo/speclaw",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },