mdkg 0.0.2 → 0.0.4

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 (50) hide show
  1. package/README.md +171 -151
  2. package/dist/cli.js +920 -422
  3. package/dist/commands/checkpoint.js +17 -6
  4. package/dist/commands/doctor.js +156 -0
  5. package/dist/commands/event.js +46 -0
  6. package/dist/commands/event_support.js +146 -0
  7. package/dist/commands/format.js +6 -7
  8. package/dist/commands/index.js +10 -4
  9. package/dist/commands/init.js +202 -11
  10. package/dist/commands/list.js +18 -1
  11. package/dist/commands/new.js +30 -5
  12. package/dist/commands/pack.js +332 -10
  13. package/dist/commands/query_output.js +84 -0
  14. package/dist/commands/search.js +22 -5
  15. package/dist/commands/show.js +26 -11
  16. package/dist/commands/skill.js +359 -0
  17. package/dist/commands/skill_support.js +121 -0
  18. package/dist/commands/task.js +270 -0
  19. package/dist/commands/validate.js +104 -7
  20. package/dist/graph/edges.js +2 -2
  21. package/dist/graph/frontmatter.js +1 -0
  22. package/dist/graph/indexer.js +21 -0
  23. package/dist/graph/node.js +20 -4
  24. package/dist/graph/skills_index_cache.js +94 -0
  25. package/dist/graph/skills_indexer.js +160 -0
  26. package/dist/init/README.md +43 -0
  27. package/dist/init/core/rule-1-mdkg-conventions.md +9 -2
  28. package/dist/init/core/rule-3-cli-contract.md +73 -14
  29. package/dist/init/core/rule-4-repo-safety-and-ignores.md +9 -3
  30. package/dist/init/core/rule-6-templates-and-schemas.md +6 -2
  31. package/dist/init/skills/SKILL.md.example +41 -0
  32. package/dist/init/templates/default/bug.md +1 -0
  33. package/dist/init/templates/default/chk.md +1 -0
  34. package/dist/init/templates/default/epic.md +1 -0
  35. package/dist/init/templates/default/feat.md +1 -0
  36. package/dist/init/templates/default/task.md +1 -0
  37. package/dist/init/templates/default/test.md +1 -0
  38. package/dist/pack/budget.js +186 -0
  39. package/dist/pack/export_md.js +17 -1
  40. package/dist/pack/export_xml.js +15 -0
  41. package/dist/pack/metrics.js +66 -0
  42. package/dist/pack/pack.js +35 -0
  43. package/dist/pack/profile.js +222 -0
  44. package/dist/pack/stats.js +37 -0
  45. package/dist/templates/headings.js +34 -0
  46. package/dist/util/argparse.js +47 -1
  47. package/dist/util/filter.js +18 -0
  48. package/dist/util/id.js +23 -0
  49. package/dist/util/output.js +2 -2
  50. package/package.json +6 -2
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyPackBudgets = applyPackBudgets;
4
+ const metrics_1 = require("./metrics");
5
+ function normalizeLimit(value, label) {
6
+ if (value === undefined) {
7
+ return undefined;
8
+ }
9
+ if (!Number.isInteger(value) || value < 0) {
10
+ throw new Error(`${label} must be a non-negative integer`);
11
+ }
12
+ if (value === 0) {
13
+ return undefined;
14
+ }
15
+ return value;
16
+ }
17
+ function exceeds(limits, totals) {
18
+ if (limits.maxChars !== undefined && totals.chars > limits.maxChars) {
19
+ return true;
20
+ }
21
+ if (limits.maxLines !== undefined && totals.lines > limits.maxLines) {
22
+ return true;
23
+ }
24
+ if (limits.maxTokens !== undefined && totals.tokens_estimate > limits.maxTokens) {
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+ function exceededKinds(limits, totals) {
30
+ const kinds = [];
31
+ if (limits.maxChars !== undefined && totals.chars > limits.maxChars) {
32
+ kinds.push("chars");
33
+ }
34
+ if (limits.maxLines !== undefined && totals.lines > limits.maxLines) {
35
+ kinds.push("lines");
36
+ }
37
+ if (limits.maxTokens !== undefined && totals.tokens_estimate > limits.maxTokens) {
38
+ kinds.push("tokens");
39
+ }
40
+ return kinds;
41
+ }
42
+ function truncateBodyToFit(pack, limits) {
43
+ if (pack.nodes.length === 0) {
44
+ return pack;
45
+ }
46
+ const root = pack.nodes[0];
47
+ const sourceLines = root.body.split(/\r?\n/);
48
+ if (sourceLines.length === 0) {
49
+ return pack;
50
+ }
51
+ let low = 0;
52
+ let high = sourceLines.length;
53
+ let best = 0;
54
+ while (low <= high) {
55
+ const mid = Math.floor((low + high) / 2);
56
+ const candidateBody = sourceLines.slice(0, mid).join("\n").trimEnd();
57
+ const candidatePack = {
58
+ ...pack,
59
+ nodes: [{ ...root, body: candidateBody }],
60
+ };
61
+ const stats = (0, metrics_1.measurePack)(candidatePack);
62
+ if (!exceeds(limits, stats.totals)) {
63
+ best = mid;
64
+ low = mid + 1;
65
+ }
66
+ else {
67
+ high = mid - 1;
68
+ }
69
+ }
70
+ const trimmedBody = sourceLines.slice(0, best).join("\n").trimEnd();
71
+ return {
72
+ ...pack,
73
+ nodes: [{ ...root, body: trimmedBody }],
74
+ };
75
+ }
76
+ function applyPackBudgets(pack, limitsInput, profile) {
77
+ const limits = {
78
+ maxChars: normalizeLimit(limitsInput.maxChars, "--max-chars"),
79
+ maxLines: normalizeLimit(limitsInput.maxLines, "--max-lines"),
80
+ maxTokens: normalizeLimit(limitsInput.maxTokens, "--max-tokens"),
81
+ };
82
+ const beforeStats = (0, metrics_1.measurePack)(pack);
83
+ let working = {
84
+ meta: {
85
+ ...pack.meta,
86
+ truncated: {
87
+ ...pack.meta.truncated,
88
+ max_chars: pack.meta.truncated.max_chars ?? false,
89
+ max_lines: pack.meta.truncated.max_lines ?? false,
90
+ max_tokens: pack.meta.truncated.max_tokens ?? false,
91
+ body_truncated: pack.meta.truncated.body_truncated
92
+ ? [...pack.meta.truncated.body_truncated]
93
+ : [],
94
+ },
95
+ },
96
+ nodes: [...pack.nodes],
97
+ };
98
+ const droppedNodes = [];
99
+ const bodyTruncatedNodes = [];
100
+ let stats = (0, metrics_1.measurePack)(working);
101
+ while (working.nodes.length > 1 && exceeds(limits, stats.totals)) {
102
+ const kinds = exceededKinds(limits, stats.totals);
103
+ for (const kind of kinds) {
104
+ if (kind === "chars") {
105
+ working.meta.truncated.max_chars = true;
106
+ }
107
+ else if (kind === "lines") {
108
+ working.meta.truncated.max_lines = true;
109
+ }
110
+ else if (kind === "tokens") {
111
+ working.meta.truncated.max_tokens = true;
112
+ }
113
+ }
114
+ const dropped = working.nodes[working.nodes.length - 1];
115
+ working = {
116
+ ...working,
117
+ nodes: working.nodes.slice(0, -1),
118
+ };
119
+ if (dropped) {
120
+ droppedNodes.push(dropped.qid);
121
+ }
122
+ stats = (0, metrics_1.measurePack)(working);
123
+ }
124
+ if (exceeds(limits, stats.totals) && working.nodes.length > 0) {
125
+ const beforeBody = working.nodes[0]?.body ?? "";
126
+ const truncated = truncateBodyToFit(working, limits);
127
+ const afterBody = truncated.nodes[0]?.body ?? "";
128
+ if (afterBody !== beforeBody) {
129
+ bodyTruncatedNodes.push(truncated.nodes[0].qid);
130
+ truncated.meta.truncated.body_truncated = [
131
+ ...(truncated.meta.truncated.body_truncated ?? []),
132
+ truncated.nodes[0].qid,
133
+ ];
134
+ }
135
+ const kinds = exceededKinds(limits, stats.totals);
136
+ for (const kind of kinds) {
137
+ if (kind === "chars") {
138
+ truncated.meta.truncated.max_chars = true;
139
+ }
140
+ else if (kind === "lines") {
141
+ truncated.meta.truncated.max_lines = true;
142
+ }
143
+ else if (kind === "tokens") {
144
+ truncated.meta.truncated.max_tokens = true;
145
+ }
146
+ }
147
+ working = truncated;
148
+ stats = (0, metrics_1.measurePack)(working);
149
+ }
150
+ if (droppedNodes.length > 0) {
151
+ working.meta.truncated.dropped.push(...droppedNodes);
152
+ }
153
+ if (bodyTruncatedNodes.length > 0) {
154
+ working.meta.truncated.body_truncated = [
155
+ ...(working.meta.truncated.body_truncated ?? []),
156
+ ...bodyTruncatedNodes,
157
+ ];
158
+ }
159
+ working.meta.node_count = working.nodes.length;
160
+ const report = {
161
+ root: pack.meta.root,
162
+ profile: pack.meta.profile ?? profile,
163
+ limits: {
164
+ max_chars: limits.maxChars,
165
+ max_lines: limits.maxLines,
166
+ max_tokens: limits.maxTokens,
167
+ },
168
+ before: {
169
+ node_count: pack.nodes.length,
170
+ chars: beforeStats.totals.chars,
171
+ lines: beforeStats.totals.lines,
172
+ bytes: beforeStats.totals.bytes,
173
+ tokens_estimate: beforeStats.totals.tokens_estimate,
174
+ },
175
+ after: {
176
+ node_count: working.nodes.length,
177
+ chars: stats.totals.chars,
178
+ lines: stats.totals.lines,
179
+ bytes: stats.totals.bytes,
180
+ tokens_estimate: stats.totals.tokens_estimate,
181
+ },
182
+ dropped_nodes: droppedNodes,
183
+ body_truncated_nodes: bodyTruncatedNodes,
184
+ };
185
+ return { pack: working, report };
186
+ }
@@ -13,8 +13,20 @@ function renderHeader(meta, nodes) {
13
13
  lines.push(`root: ${meta.root}`);
14
14
  lines.push(`depth: ${meta.depth}`);
15
15
  lines.push(`verbose: ${meta.verbose}`);
16
+ if (meta.profile) {
17
+ lines.push(`profile: ${meta.profile}`);
18
+ }
19
+ if (meta.body_mode) {
20
+ lines.push(`body_mode: ${meta.body_mode}`);
21
+ }
22
+ if (meta.latest_checkpoint_qid) {
23
+ lines.push(`latest_checkpoint_qid: ${meta.latest_checkpoint_qid}`);
24
+ }
25
+ if (meta.latest_checkpoint_qid_hint) {
26
+ lines.push(`latest_checkpoint_qid_hint: ${meta.latest_checkpoint_qid_hint}`);
27
+ }
16
28
  lines.push(`nodes: ${nodes.length}`);
17
- lines.push(`truncated: max_nodes=${meta.truncated.max_nodes} max_bytes=${meta.truncated.max_bytes}`);
29
+ lines.push(`truncated: max_nodes=${meta.truncated.max_nodes} max_bytes=${meta.truncated.max_bytes} max_chars=${Boolean(meta.truncated.max_chars)} max_lines=${Boolean(meta.truncated.max_lines)} max_tokens=${Boolean(meta.truncated.max_tokens)}`);
18
30
  if (meta.truncated.dropped.length > 0) {
19
31
  lines.push(`dropped: ${meta.truncated.dropped.join(", ")}`);
20
32
  }
@@ -55,6 +67,10 @@ function cloneTruncation(truncation) {
55
67
  max_nodes: truncation.max_nodes,
56
68
  max_bytes: truncation.max_bytes,
57
69
  dropped: [...truncation.dropped],
70
+ max_chars: truncation.max_chars,
71
+ max_lines: truncation.max_lines,
72
+ max_tokens: truncation.max_tokens,
73
+ body_truncated: truncation.body_truncated ? [...truncation.body_truncated] : [],
58
74
  };
59
75
  }
60
76
  function buildMeta(meta, nodes) {
@@ -29,11 +29,26 @@ function exportXml(pack) {
29
29
  lines.push(` <root>${escapeXml(pack.meta.root)}</root>`);
30
30
  lines.push(` <depth>${pack.meta.depth}</depth>`);
31
31
  lines.push(` <verbose>${pack.meta.verbose}</verbose>`);
32
+ if (pack.meta.profile) {
33
+ lines.push(` <profile>${escapeXml(pack.meta.profile)}</profile>`);
34
+ }
35
+ if (pack.meta.body_mode) {
36
+ lines.push(` <body_mode>${escapeXml(pack.meta.body_mode)}</body_mode>`);
37
+ }
38
+ if (pack.meta.latest_checkpoint_qid) {
39
+ lines.push(` <latest_checkpoint_qid>${escapeXml(pack.meta.latest_checkpoint_qid)}</latest_checkpoint_qid>`);
40
+ }
41
+ if (pack.meta.latest_checkpoint_qid_hint) {
42
+ lines.push(` <latest_checkpoint_qid_hint>${escapeXml(pack.meta.latest_checkpoint_qid_hint)}</latest_checkpoint_qid_hint>`);
43
+ }
32
44
  lines.push(` <generated_at>${escapeXml(pack.meta.generated_at)}</generated_at>`);
33
45
  lines.push(` <node_count>${pack.meta.node_count}</node_count>`);
34
46
  lines.push(" <truncated>");
35
47
  lines.push(` <max_nodes>${pack.meta.truncated.max_nodes}</max_nodes>`);
36
48
  lines.push(` <max_bytes>${pack.meta.truncated.max_bytes}</max_bytes>`);
49
+ lines.push(` <max_chars>${Boolean(pack.meta.truncated.max_chars)}</max_chars>`);
50
+ lines.push(` <max_lines>${Boolean(pack.meta.truncated.max_lines)}</max_lines>`);
51
+ lines.push(` <max_tokens>${Boolean(pack.meta.truncated.max_tokens)}</max_tokens>`);
37
52
  if (pack.meta.truncated.dropped.length > 0) {
38
53
  lines.push(" <dropped>");
39
54
  for (const qid of pack.meta.truncated.dropped) {
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.countLines = countLines;
4
+ exports.estimateTokensFromChars = estimateTokensFromChars;
5
+ exports.renderNodeMetricsText = renderNodeMetricsText;
6
+ exports.measureNode = measureNode;
7
+ exports.measurePack = measurePack;
8
+ function countLines(text) {
9
+ if (text.length === 0) {
10
+ return 0;
11
+ }
12
+ return text.split(/\r?\n/).length;
13
+ }
14
+ function estimateTokensFromChars(chars) {
15
+ if (chars <= 0) {
16
+ return 0;
17
+ }
18
+ return Math.ceil(chars / 4);
19
+ }
20
+ function renderNodeMetricsText(node) {
21
+ const lines = [];
22
+ lines.push(`qid: ${node.qid}`);
23
+ lines.push(`id: ${node.id}`);
24
+ lines.push(`workspace: ${node.workspace}`);
25
+ lines.push(`type: ${node.type}`);
26
+ lines.push(`title: ${node.title}`);
27
+ if (node.status) {
28
+ lines.push(`status: ${node.status}`);
29
+ }
30
+ if (node.priority !== undefined) {
31
+ lines.push(`priority: ${node.priority}`);
32
+ }
33
+ lines.push(`path: ${node.path}`);
34
+ lines.push(`links: ${node.links.join(",")}`);
35
+ lines.push(`artifacts: ${node.artifacts.join(",")}`);
36
+ lines.push(`refs: ${node.refs.join(",")}`);
37
+ lines.push(`aliases: ${node.aliases.join(",")}`);
38
+ if (node.body.length > 0) {
39
+ lines.push("");
40
+ lines.push(node.body);
41
+ }
42
+ return lines.join("\n");
43
+ }
44
+ function measureNode(node) {
45
+ const rendered = renderNodeMetricsText(node);
46
+ const chars = rendered.length;
47
+ const lines = countLines(rendered);
48
+ const bytes = Buffer.byteLength(rendered, "utf8");
49
+ return {
50
+ qid: node.qid,
51
+ chars,
52
+ lines,
53
+ bytes,
54
+ tokens_estimate: estimateTokensFromChars(chars),
55
+ };
56
+ }
57
+ function measurePack(pack) {
58
+ const nodes = pack.nodes.map((node) => measureNode(node));
59
+ const totals = nodes.reduce((acc, node) => ({
60
+ chars: acc.chars + node.chars,
61
+ lines: acc.lines + node.lines,
62
+ bytes: acc.bytes + node.bytes,
63
+ tokens_estimate: acc.tokens_estimate + node.tokens_estimate,
64
+ }), { chars: 0, lines: 0, bytes: 0, tokens_estimate: 0 });
65
+ return { nodes, totals };
66
+ }
package/dist/pack/pack.js CHANGED
@@ -130,6 +130,24 @@ function buildPackNode(root, index, qid) {
130
130
  function mergeWarnings(warnings, message) {
131
131
  warnings.push(message);
132
132
  }
133
+ function checkpointWorkspaceFromQid(qid) {
134
+ const [workspace] = qid.split(":");
135
+ return workspace ?? "root";
136
+ }
137
+ function resolveLatestCheckpointQid(index, workspace) {
138
+ const candidates = Object.values(index.nodes)
139
+ .filter((node) => node.ws === workspace && node.type === "checkpoint")
140
+ .sort((a, b) => {
141
+ if (a.updated !== b.updated) {
142
+ return b.updated.localeCompare(a.updated);
143
+ }
144
+ if (a.created !== b.created) {
145
+ return b.created.localeCompare(a.created);
146
+ }
147
+ return b.qid.localeCompare(a.qid);
148
+ });
149
+ return candidates[0]?.qid;
150
+ }
133
151
  function applyMaxNodes(orderedQids, maxNodes, truncation) {
134
152
  if (maxNodes <= 0 || orderedQids.length <= maxNodes) {
135
153
  return { included: orderedQids, dropped: [] };
@@ -142,7 +160,22 @@ function applyMaxNodes(orderedQids, maxNodes, truncation) {
142
160
  }
143
161
  function buildPack(options) {
144
162
  const warnings = [];
163
+ const includeLatestCheckpoint = options.includeLatestCheckpoint ?? true;
145
164
  const { qids, depths } = collectNodes(options.index, options.rootQid, options.depth, options.edges);
165
+ const workspace = checkpointWorkspaceFromQid(options.rootQid);
166
+ const latestCheckpointHint = options.index.meta.latest_checkpoint_qid?.[workspace];
167
+ const latestCheckpointResolved = includeLatestCheckpoint
168
+ ? resolveLatestCheckpointQid(options.index, workspace)
169
+ : undefined;
170
+ if (latestCheckpointResolved && latestCheckpointResolved !== options.rootQid) {
171
+ qids.add(latestCheckpointResolved);
172
+ }
173
+ if (includeLatestCheckpoint &&
174
+ latestCheckpointHint &&
175
+ latestCheckpointResolved &&
176
+ latestCheckpointHint !== latestCheckpointResolved) {
177
+ mergeWarnings(warnings, `latest_checkpoint_qid hint mismatch for ${workspace}: ${latestCheckpointHint} -> ${latestCheckpointResolved}`);
178
+ }
146
179
  if (options.verbose) {
147
180
  const coreIds = (0, verbose_core_1.readVerboseCoreList)(options.verboseCoreListPath);
148
181
  for (const id of coreIds) {
@@ -173,6 +206,8 @@ function buildPack(options) {
173
206
  verbose: options.verbose,
174
207
  generated_at: new Date().toISOString(),
175
208
  node_count: nodes.length,
209
+ latest_checkpoint_qid: latestCheckpointResolved,
210
+ latest_checkpoint_qid_hint: latestCheckpointHint,
176
211
  truncated: truncation,
177
212
  },
178
213
  nodes,
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listPackProfiles = listPackProfiles;
4
+ exports.resolvePackProfile = resolvePackProfile;
5
+ exports.shapePackBodies = shapePackBodies;
6
+ const PACK_PROFILE_CATALOG = [
7
+ {
8
+ profile: "standard",
9
+ bodyMode: "full",
10
+ description: "Keep full node bodies (current default behavior).",
11
+ defaults: [],
12
+ },
13
+ {
14
+ profile: "concise",
15
+ bodyMode: "summary",
16
+ description: "Use summary-first excerpts for each node body.",
17
+ defaults: ["strip_code=true"],
18
+ },
19
+ {
20
+ profile: "headers",
21
+ bodyMode: "none",
22
+ description: "Frontmatter/metadata only; remove all node body content.",
23
+ defaults: [],
24
+ },
25
+ ];
26
+ const CODE_FENCE_RE = /^(```|~~~)/;
27
+ function normalizeHeading(value) {
28
+ return value.trim().toLowerCase();
29
+ }
30
+ function removeCodeBlocks(body) {
31
+ const lines = body.split(/\r?\n/);
32
+ const result = [];
33
+ let inFence = false;
34
+ for (const line of lines) {
35
+ if (CODE_FENCE_RE.test(line.trimStart())) {
36
+ inFence = !inFence;
37
+ if (!inFence) {
38
+ result.push("[code block omitted]");
39
+ }
40
+ continue;
41
+ }
42
+ if (!inFence) {
43
+ result.push(line);
44
+ }
45
+ }
46
+ return result.join("\n");
47
+ }
48
+ function capCodeBlocks(body, maxCodeLines) {
49
+ const lines = body.split(/\r?\n/);
50
+ const result = [];
51
+ let inFence = false;
52
+ let kept = 0;
53
+ let truncated = false;
54
+ for (const line of lines) {
55
+ if (CODE_FENCE_RE.test(line.trimStart())) {
56
+ if (inFence && truncated) {
57
+ result.push("[code lines truncated]");
58
+ }
59
+ inFence = !inFence;
60
+ kept = 0;
61
+ truncated = false;
62
+ result.push(line);
63
+ continue;
64
+ }
65
+ if (!inFence) {
66
+ result.push(line);
67
+ continue;
68
+ }
69
+ if (kept < maxCodeLines) {
70
+ result.push(line);
71
+ kept += 1;
72
+ continue;
73
+ }
74
+ truncated = true;
75
+ }
76
+ if (inFence && truncated) {
77
+ result.push("[code lines truncated]");
78
+ }
79
+ return result.join("\n");
80
+ }
81
+ function applyCodeTransform(body, stripCode, maxCodeLines) {
82
+ if (body.length === 0) {
83
+ return body;
84
+ }
85
+ if (stripCode) {
86
+ return removeCodeBlocks(body);
87
+ }
88
+ if (maxCodeLines !== undefined) {
89
+ return capCodeBlocks(body, maxCodeLines);
90
+ }
91
+ return body;
92
+ }
93
+ function splitSections(body) {
94
+ const lines = body.split(/\r?\n/);
95
+ const sections = [{ lines: [] }];
96
+ let current = sections[0];
97
+ let inFence = false;
98
+ for (const line of lines) {
99
+ const trimmed = line.trimStart();
100
+ if (CODE_FENCE_RE.test(trimmed)) {
101
+ inFence = !inFence;
102
+ current.lines.push(line);
103
+ continue;
104
+ }
105
+ if (!inFence) {
106
+ const match = /^#+\s+(.*)$/.exec(line);
107
+ if (match) {
108
+ current = {
109
+ heading: normalizeHeading(match[1] ?? ""),
110
+ headingLine: line,
111
+ lines: [],
112
+ };
113
+ sections.push(current);
114
+ continue;
115
+ }
116
+ }
117
+ current.lines.push(line);
118
+ }
119
+ return sections;
120
+ }
121
+ function firstNonEmptyLines(lines, maxLines) {
122
+ const result = [];
123
+ for (const line of lines) {
124
+ if (line.trim().length === 0) {
125
+ continue;
126
+ }
127
+ result.push(line);
128
+ if (result.length >= maxLines) {
129
+ break;
130
+ }
131
+ }
132
+ return result;
133
+ }
134
+ function extractSummary(body, preferredHeadings, summaryMaxLines) {
135
+ if (body.trim().length === 0) {
136
+ return "";
137
+ }
138
+ const sections = splitSections(body);
139
+ for (const heading of preferredHeadings) {
140
+ const section = sections.find((candidate) => candidate.heading === heading);
141
+ if (!section) {
142
+ continue;
143
+ }
144
+ const excerpt = firstNonEmptyLines(section.lines, summaryMaxLines);
145
+ if (excerpt.length === 0) {
146
+ continue;
147
+ }
148
+ const summaryLines = section.headingLine ? [section.headingLine, ...excerpt] : excerpt;
149
+ return summaryLines.join("\n").trimEnd();
150
+ }
151
+ const fallback = firstNonEmptyLines(body.split(/\r?\n/), summaryMaxLines);
152
+ return fallback.join("\n").trimEnd();
153
+ }
154
+ function shapeNodeBody(node, options) {
155
+ const { resolved, templateHeadingMap } = options;
156
+ let shaped = node.body;
157
+ if (resolved.bodyMode === "none") {
158
+ shaped = "";
159
+ }
160
+ else if (resolved.bodyMode === "summary") {
161
+ const preferred = templateHeadingMap[node.type] ?? [];
162
+ shaped = extractSummary(node.body, preferred, resolved.summaryMaxLines);
163
+ }
164
+ shaped = applyCodeTransform(shaped, resolved.stripCode, resolved.maxCodeLines);
165
+ return shaped.trimEnd();
166
+ }
167
+ function listPackProfiles() {
168
+ return PACK_PROFILE_CATALOG.map((entry) => ({
169
+ profile: entry.profile,
170
+ bodyMode: entry.bodyMode,
171
+ description: entry.description,
172
+ defaults: [...entry.defaults],
173
+ }));
174
+ }
175
+ function resolvePackProfile(input) {
176
+ if (input.maxCodeLines !== undefined && (!Number.isInteger(input.maxCodeLines) || input.maxCodeLines < 0)) {
177
+ throw new Error("--max-code-lines must be a non-negative integer");
178
+ }
179
+ let profileRaw = input.profile;
180
+ if (input.concise) {
181
+ if (profileRaw && profileRaw.toLowerCase() !== "concise") {
182
+ throw new Error("--concise conflicts with --pack-profile");
183
+ }
184
+ profileRaw = "concise";
185
+ }
186
+ const normalized = (profileRaw ?? "standard").toLowerCase();
187
+ if (normalized !== "standard" && normalized !== "concise" && normalized !== "headers") {
188
+ throw new Error("--pack-profile must be one of standard, concise, headers");
189
+ }
190
+ const profile = normalized;
191
+ let bodyMode = "full";
192
+ let defaultStripCode = false;
193
+ if (profile === "concise") {
194
+ bodyMode = "summary";
195
+ defaultStripCode = true;
196
+ }
197
+ else if (profile === "headers") {
198
+ bodyMode = "none";
199
+ }
200
+ return {
201
+ profile,
202
+ bodyMode,
203
+ stripCode: input.stripCode || defaultStripCode,
204
+ maxCodeLines: input.maxCodeLines,
205
+ summaryMaxLines: 12,
206
+ };
207
+ }
208
+ function shapePackBodies(pack, options) {
209
+ const nodes = pack.nodes.map((node) => ({
210
+ ...node,
211
+ body: shapeNodeBody(node, options),
212
+ }));
213
+ return {
214
+ meta: {
215
+ ...pack.meta,
216
+ node_count: nodes.length,
217
+ profile: options.resolved.profile,
218
+ body_mode: options.resolved.bodyMode,
219
+ },
220
+ nodes,
221
+ };
222
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderPackStats = renderPackStats;
4
+ function pad(value, width) {
5
+ if (value.length >= width) {
6
+ return value;
7
+ }
8
+ return `${value}${" ".repeat(width - value.length)}`;
9
+ }
10
+ function padLeft(value, width) {
11
+ if (value.length >= width) {
12
+ return value;
13
+ }
14
+ return `${" ".repeat(width - value.length)}${value}`;
15
+ }
16
+ function renderPackStats(stats) {
17
+ const rows = stats.nodes.map((node) => ({
18
+ qid: node.qid,
19
+ chars: String(node.chars),
20
+ lines: String(node.lines),
21
+ bytes: String(node.bytes),
22
+ tokens: String(node.tokens_estimate),
23
+ }));
24
+ const qidWidth = Math.max(3, ...rows.map((row) => row.qid.length), 5);
25
+ const charsWidth = Math.max(5, ...rows.map((row) => row.chars.length), String(stats.totals.chars).length);
26
+ const linesWidth = Math.max(5, ...rows.map((row) => row.lines.length), String(stats.totals.lines).length);
27
+ const bytesWidth = Math.max(5, ...rows.map((row) => row.bytes.length), String(stats.totals.bytes).length);
28
+ const tokensWidth = Math.max(6, ...rows.map((row) => row.tokens.length), String(stats.totals.tokens_estimate).length);
29
+ const lines = [];
30
+ lines.push(`${pad("qid", qidWidth)} ${padLeft("chars", charsWidth)} ${padLeft("lines", linesWidth)} ${padLeft("bytes", bytesWidth)} ${padLeft("tokens", tokensWidth)}`);
31
+ lines.push(`${"-".repeat(qidWidth)} ${"-".repeat(charsWidth)} ${"-".repeat(linesWidth)} ${"-".repeat(bytesWidth)} ${"-".repeat(tokensWidth)}`);
32
+ for (const row of rows) {
33
+ lines.push(`${pad(row.qid, qidWidth)} ${padLeft(row.chars, charsWidth)} ${padLeft(row.lines, linesWidth)} ${padLeft(row.bytes, bytesWidth)} ${padLeft(row.tokens, tokensWidth)}`);
34
+ }
35
+ lines.push(`${pad("TOTAL", qidWidth)} ${padLeft(String(stats.totals.chars), charsWidth)} ${padLeft(String(stats.totals.lines), linesWidth)} ${padLeft(String(stats.totals.bytes), bytesWidth)} ${padLeft(String(stats.totals.tokens_estimate), tokensWidth)}`);
36
+ return lines.join("\n");
37
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractMarkdownHeadings = extractMarkdownHeadings;
4
+ exports.loadTemplateHeadingMap = loadTemplateHeadingMap;
5
+ const loader_1 = require("./loader");
6
+ function normalizeHeading(value) {
7
+ return value.trim().toLowerCase();
8
+ }
9
+ function extractMarkdownHeadings(body) {
10
+ const lines = body.split(/\r?\n/);
11
+ const headings = [];
12
+ const seen = new Set();
13
+ for (const line of lines) {
14
+ const match = /^#+\s+(.*)$/.exec(line);
15
+ if (!match) {
16
+ continue;
17
+ }
18
+ const heading = normalizeHeading(match[1] ?? "");
19
+ if (!heading || seen.has(heading)) {
20
+ continue;
21
+ }
22
+ headings.push(heading);
23
+ seen.add(heading);
24
+ }
25
+ return headings;
26
+ }
27
+ function loadTemplateHeadingMap(root, config, types) {
28
+ const map = {};
29
+ for (const type of types) {
30
+ const template = (0, loader_1.loadTemplate)(root, config, type);
31
+ map[type] = extractMarkdownHeadings(template.body);
32
+ }
33
+ return map;
34
+ }