sf-agentpmd 0.1.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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +26 -0
  3. package/README.md +204 -0
  4. package/bin/dev.js +5 -0
  5. package/bin/run.js +3 -0
  6. package/dist/analyzer/action-references.d.ts +21 -0
  7. package/dist/analyzer/action-references.js +130 -0
  8. package/dist/analyzer/action-references.js.map +1 -0
  9. package/dist/analyzer/analyze.d.ts +43 -0
  10. package/dist/analyzer/analyze.js +222 -0
  11. package/dist/analyzer/analyze.js.map +1 -0
  12. package/dist/analyzer/apex-analyze.d.ts +14 -0
  13. package/dist/analyzer/apex-analyze.js +60 -0
  14. package/dist/analyzer/apex-analyze.js.map +1 -0
  15. package/dist/analyzer/apex-complexity.d.ts +27 -0
  16. package/dist/analyzer/apex-complexity.js +133 -0
  17. package/dist/analyzer/apex-complexity.js.map +1 -0
  18. package/dist/analyzer/apex-parse.d.ts +39 -0
  19. package/dist/analyzer/apex-parse.js +32 -0
  20. package/dist/analyzer/apex-parse.js.map +1 -0
  21. package/dist/analyzer/apex-resolve.d.ts +32 -0
  22. package/dist/analyzer/apex-resolve.js +59 -0
  23. package/dist/analyzer/apex-resolve.js.map +1 -0
  24. package/dist/analyzer/complexity.d.ts +30 -0
  25. package/dist/analyzer/complexity.js +126 -0
  26. package/dist/analyzer/complexity.js.map +1 -0
  27. package/dist/analyzer/parse.d.ts +51 -0
  28. package/dist/analyzer/parse.js +143 -0
  29. package/dist/analyzer/parse.js.map +1 -0
  30. package/dist/analyzer/project.d.ts +12 -0
  31. package/dist/analyzer/project.js +51 -0
  32. package/dist/analyzer/project.js.map +1 -0
  33. package/dist/analyzer/types.d.ts +76 -0
  34. package/dist/analyzer/types.js +2 -0
  35. package/dist/analyzer/types.js.map +1 -0
  36. package/dist/commands/agentpmd/analyze.d.ts +20 -0
  37. package/dist/commands/agentpmd/analyze.js +122 -0
  38. package/dist/commands/agentpmd/analyze.js.map +1 -0
  39. package/dist/commands/agentpmd/install-skill.d.ts +11 -0
  40. package/dist/commands/agentpmd/install-skill.js +33 -0
  41. package/dist/commands/agentpmd/install-skill.js.map +1 -0
  42. package/dist/index.d.ts +3 -0
  43. package/dist/index.js +3 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/renderers/csv.d.ts +6 -0
  46. package/dist/renderers/csv.js +78 -0
  47. package/dist/renderers/csv.js.map +1 -0
  48. package/dist/renderers/index.d.ts +8 -0
  49. package/dist/renderers/index.js +25 -0
  50. package/dist/renderers/index.js.map +1 -0
  51. package/dist/renderers/markdown.d.ts +12 -0
  52. package/dist/renderers/markdown.js +233 -0
  53. package/dist/renderers/markdown.js.map +1 -0
  54. package/dist/renderers/options.d.ts +20 -0
  55. package/dist/renderers/options.js +2 -0
  56. package/dist/renderers/options.js.map +1 -0
  57. package/dist/renderers/sarif.d.ts +3 -0
  58. package/dist/renderers/sarif.js +131 -0
  59. package/dist/renderers/sarif.js.map +1 -0
  60. package/dist/renderers/text.d.ts +3 -0
  61. package/dist/renderers/text.js +243 -0
  62. package/dist/renderers/text.js.map +1 -0
  63. package/oclif.manifest.json +168 -0
  64. package/package.json +97 -0
  65. package/skill/SKILL.md +103 -0
  66. package/skill/references/command-structure.md +89 -0
  67. package/skill/references/install.md +112 -0
  68. package/skill/references/output-formats.md +205 -0
  69. package/skill/references/upgrade.md +112 -0
  70. package/vendor/agentscript-parser-javascript/dist/cst-node.d.ts +83 -0
  71. package/vendor/agentscript-parser-javascript/dist/cst-node.js +238 -0
  72. package/vendor/agentscript-parser-javascript/dist/errors.d.ts +34 -0
  73. package/vendor/agentscript-parser-javascript/dist/errors.js +74 -0
  74. package/vendor/agentscript-parser-javascript/dist/expressions.d.ts +36 -0
  75. package/vendor/agentscript-parser-javascript/dist/expressions.js +682 -0
  76. package/vendor/agentscript-parser-javascript/dist/highlighter.d.ts +24 -0
  77. package/vendor/agentscript-parser-javascript/dist/highlighter.js +260 -0
  78. package/vendor/agentscript-parser-javascript/dist/index.d.ts +29 -0
  79. package/vendor/agentscript-parser-javascript/dist/index.js +35 -0
  80. package/vendor/agentscript-parser-javascript/dist/lexer.d.ts +60 -0
  81. package/vendor/agentscript-parser-javascript/dist/lexer.js +630 -0
  82. package/vendor/agentscript-parser-javascript/dist/parse-mapping.d.ts +46 -0
  83. package/vendor/agentscript-parser-javascript/dist/parse-mapping.js +549 -0
  84. package/vendor/agentscript-parser-javascript/dist/parse-sequence.d.ts +10 -0
  85. package/vendor/agentscript-parser-javascript/dist/parse-sequence.js +118 -0
  86. package/vendor/agentscript-parser-javascript/dist/parse-statements.d.ts +15 -0
  87. package/vendor/agentscript-parser-javascript/dist/parse-statements.js +519 -0
  88. package/vendor/agentscript-parser-javascript/dist/parse-templates.d.ts +15 -0
  89. package/vendor/agentscript-parser-javascript/dist/parse-templates.js +323 -0
  90. package/vendor/agentscript-parser-javascript/dist/parser.d.ts +65 -0
  91. package/vendor/agentscript-parser-javascript/dist/parser.js +163 -0
  92. package/vendor/agentscript-parser-javascript/dist/recovery.d.ts +51 -0
  93. package/vendor/agentscript-parser-javascript/dist/recovery.js +199 -0
  94. package/vendor/agentscript-parser-javascript/dist/token.d.ts +58 -0
  95. package/vendor/agentscript-parser-javascript/dist/token.js +62 -0
  96. package/vendor/agentscript-parser-javascript/package.json +19 -0
  97. package/vendor/agentscript-types/dist/comment.d.ts +11 -0
  98. package/vendor/agentscript-types/dist/comment.js +10 -0
  99. package/vendor/agentscript-types/dist/cst.d.ts +7 -0
  100. package/vendor/agentscript-types/dist/cst.js +8 -0
  101. package/vendor/agentscript-types/dist/diagnostic.d.ts +34 -0
  102. package/vendor/agentscript-types/dist/diagnostic.js +23 -0
  103. package/vendor/agentscript-types/dist/index.d.ts +9 -0
  104. package/vendor/agentscript-types/dist/index.js +10 -0
  105. package/vendor/agentscript-types/dist/position.d.ts +11 -0
  106. package/vendor/agentscript-types/dist/position.js +16 -0
  107. package/vendor/agentscript-types/dist/syntax-node.d.ts +39 -0
  108. package/vendor/agentscript-types/dist/syntax-node.js +8 -0
  109. package/vendor/agentscript-types/package.json +15 -0
@@ -0,0 +1,323 @@
1
+ /*
2
+ * Copyright (c) 2026, Salesforce, Inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ * For full license text, see the LICENSE file in the repo root or https://www.apache.org/licenses/LICENSE-2.0
6
+ */
7
+ /**
8
+ * Template-parsing functions extracted from Parser class.
9
+ *
10
+ * Each function takes a ParserContext as its first parameter, following
11
+ * the same free-function pattern as parse-statements.ts and expressions.ts.
12
+ *
13
+ * Template indentation state (templateOuterIndent) is computed locally in
14
+ * parseTemplate and passed as an explicit parameter to templateContinues.
15
+ */
16
+ import { TokenKind } from './token.js';
17
+ import { CSTNode } from './cst-node.js';
18
+ import { makeEmptyError, makeMissing, synchronize, isAtEnd, } from './recovery.js';
19
+ import { parseExpression, wrapExpression } from './expressions.js';
20
+ // ---------------------------------------------------------------------------
21
+ // Exported template parsers
22
+ // ---------------------------------------------------------------------------
23
+ /**
24
+ * Parse a template starting with `|`.
25
+ * Consumes tokens from the lexer stream, treating everything as template content
26
+ * except `{!...}` breaks which are parsed as template expressions.
27
+ */
28
+ export function parseTemplate(ctx) {
29
+ const startTok = ctx.peek();
30
+ const node = ctx.startNode('template');
31
+ // Compute the indent level of the line containing `|`.
32
+ // Tree-sitter uses *array_back(&scanner->indents) — the top of the indent
33
+ // stack, which equals the line indent. We scan backward in the source to
34
+ // measure the leading whitespace on this line.
35
+ const pipeOffset = ctx.peekOffset();
36
+ let lineStart = pipeOffset;
37
+ while (lineStart > 0 &&
38
+ ctx.source.charCodeAt(lineStart - 1) !== 10 /* \n */) {
39
+ lineStart--;
40
+ }
41
+ let templateOuterIndent = 0;
42
+ for (let i = lineStart; i < pipeOffset; i++) {
43
+ const ch = ctx.source.charCodeAt(i);
44
+ if (ch === 32 /* space */)
45
+ templateOuterIndent += 1;
46
+ else if (ch === 9 /* tab */)
47
+ templateOuterIndent += 3;
48
+ else
49
+ break;
50
+ }
51
+ // Consume the | token and track position right after it
52
+ const pipeToken = ctx.consume();
53
+ ctx.addAnonymousChild(node, pipeToken);
54
+ // If there are tokens on the same line after |, pass afterPipeOffset
55
+ // so whitespace between | and {! is captured as template_content.
56
+ // If the line is empty after |, don't pass it (avoids phantom content).
57
+ const hasContentOnSameLine = !isAtEnd(ctx) &&
58
+ ctx.peekKind() !== TokenKind.NEWLINE &&
59
+ ctx.peekKind() !== TokenKind.INDENT &&
60
+ ctx.peekKind() !== TokenKind.DEDENT;
61
+ if (hasContentOnSameLine) {
62
+ const afterPipeOffset = pipeToken.startOffset + 1;
63
+ gatherTemplateContentLine(ctx, node, afterPipeOffset);
64
+ }
65
+ // Consume NEWLINE if present
66
+ if (ctx.peekKind() === TokenKind.NEWLINE) {
67
+ ctx.consume();
68
+ }
69
+ // If there's an INDENT, the template continues on indented lines.
70
+ // Templates consume ALL indented content until we fully return to the
71
+ // base indent. We track indent depth: each INDENT increments, each
72
+ // DEDENT decrements. When depth reaches 0, a final DEDENT exits.
73
+ // Mid-template DEDENTs (under-indented continuation lines) are consumed
74
+ // as content.
75
+ if (ctx.peekKind() === TokenKind.INDENT) {
76
+ ctx.consume(); // outer INDENT
77
+ let indentDepth = 1;
78
+ while (!isAtEnd(ctx)) {
79
+ const tok = ctx.peek();
80
+ if (tok.kind === TokenKind.DEDENT) {
81
+ indentDepth--;
82
+ ctx.consume();
83
+ if (indentDepth <= 0) {
84
+ // Check if template continues with under-indented content.
85
+ // If the next meaningful token is content (not EOF/DEDENT),
86
+ // the template has under-indented continuation lines.
87
+ if (templateContinues(ctx, templateOuterIndent)) {
88
+ // Re-enter: consume content at the new (lower) indent
89
+ indentDepth = 0; // will re-increment on next INDENT
90
+ continue;
91
+ }
92
+ break;
93
+ }
94
+ }
95
+ else if (tok.kind === TokenKind.INDENT) {
96
+ indentDepth++;
97
+ ctx.consume();
98
+ }
99
+ else if (tok.kind === TokenKind.NEWLINE) {
100
+ ctx.consume();
101
+ }
102
+ else {
103
+ // When at the template's base indent depth, check if the next
104
+ // token should continue the template (e.g. comments at the base
105
+ // level should not be absorbed as template content).
106
+ if (indentDepth <= 0 && !templateContinues(ctx, templateOuterIndent)) {
107
+ break;
108
+ }
109
+ // For continuation lines, start the content from the end of the
110
+ // last template child so that newlines + indentation between a
111
+ // template_expression and the next template_content are preserved
112
+ // in the source text. (mergeTemplateContent handles this for
113
+ // consecutive template_content nodes, but not across expressions.)
114
+ const lastChild = node.children.length > 0
115
+ ? node.children[node.children.length - 1]
116
+ : null;
117
+ const gapOffset = lastChild && lastChild.endOffset < ctx.peekOffset()
118
+ ? lastChild.endOffset
119
+ : undefined;
120
+ const gapPos = gapOffset !== undefined ? lastChild.endPosition : undefined;
121
+ gatherTemplateContentLine(ctx, node, gapOffset, gapPos);
122
+ }
123
+ }
124
+ }
125
+ // Merge consecutive template_content children into single nodes.
126
+ // Tree-sitter produces one template_content per contiguous text span;
127
+ // our line-by-line parsing creates one per line.
128
+ mergeTemplateContent(ctx, node);
129
+ ctx.finishNode(node, startTok);
130
+ return node;
131
+ }
132
+ /**
133
+ * Parse a template in colinear position (after a colon on the same line).
134
+ * Currently identical to parseTemplate; kept as a separate entry point
135
+ * for semantic clarity and potential future divergence.
136
+ */
137
+ export function parseTemplateAsColinear(ctx) {
138
+ return parseTemplate(ctx);
139
+ }
140
+ // ---------------------------------------------------------------------------
141
+ // Internal helpers
142
+ // ---------------------------------------------------------------------------
143
+ /**
144
+ * Check if the template continues with under-indented content.
145
+ * After a DEDENT brings us to depth 0, if the next meaningful token
146
+ * is content (not EOF, not DEDENT, not a mapping key pattern), the
147
+ * template has continuation lines.
148
+ */
149
+ function templateContinues(ctx, templateOuterIndent) {
150
+ let i = 0;
151
+ while (ctx.peekAt(i).kind === TokenKind.NEWLINE)
152
+ i++;
153
+ const tok = ctx.peekAt(i);
154
+ // If we see content (ID, etc.) that's NOT a mapping key pattern, continue
155
+ if (tok.kind === TokenKind.EOF || tok.kind === TokenKind.DEDENT)
156
+ return false;
157
+ // Content deeper than the template's base indent is always template content,
158
+ // regardless of keywords. Matches tree-sitter scanner behavior where
159
+ // indent_length > out_of_template_indent_length keeps content in the template.
160
+ if (tok.start.column > templateOuterIndent)
161
+ return true;
162
+ // Another pipe starts a new template — don't absorb it
163
+ if (tok.kind === TokenKind.PIPE)
164
+ return false;
165
+ // If it looks like a mapping key (ID followed by COLON), template is done
166
+ if (tok.kind === TokenKind.ID || tok.kind === TokenKind.STRING) {
167
+ const after = ctx.peekAt(i + 1);
168
+ if (after.kind === TokenKind.COLON)
169
+ return false;
170
+ // Two-word key check
171
+ if (after.kind === TokenKind.ID) {
172
+ const afterAfter = ctx.peekAt(i + 2);
173
+ if (afterAfter.kind === TokenKind.COLON)
174
+ return false;
175
+ }
176
+ }
177
+ // Statement keywords terminate template continuation — they're
178
+ // sibling statements, not template content
179
+ if (tok.kind === TokenKind.ID) {
180
+ switch (tok.text) {
181
+ case 'if':
182
+ case 'elif':
183
+ case 'else':
184
+ case 'run':
185
+ case 'set':
186
+ case 'transition':
187
+ return false;
188
+ case 'with':
189
+ // "with" not followed by colon is a statement
190
+ if (ctx.peekAt(i + 1).kind !== TokenKind.COLON)
191
+ return false;
192
+ break;
193
+ case 'available':
194
+ if (ctx.peekAt(i + 1).kind === TokenKind.ID &&
195
+ ctx.peekAt(i + 1).text === 'when')
196
+ return false;
197
+ break;
198
+ }
199
+ }
200
+ // If it looks like a dash (sequence), template is done
201
+ if (tok.kind === TokenKind.DASH_SPACE)
202
+ return false;
203
+ // Comments at the template's base indent level are not template content
204
+ if (tok.kind === TokenKind.COMMENT)
205
+ return false;
206
+ // Otherwise, assume it's template continuation
207
+ return true;
208
+ }
209
+ /** Merge consecutive template_content children into single nodes. */
210
+ function mergeTemplateContent(ctx, template) {
211
+ const merged = [];
212
+ let i = 0;
213
+ while (i < template.children.length) {
214
+ const child = template.children[i];
215
+ if (child.type === 'template_content') {
216
+ // Find the run of consecutive template_content nodes
217
+ let end = i + 1;
218
+ while (end < template.children.length &&
219
+ template.children[end].type === 'template_content') {
220
+ end++;
221
+ }
222
+ if (end > i + 1) {
223
+ // Merge into one node
224
+ const first = template.children[i];
225
+ const last = template.children[end - 1];
226
+ const mergedNode = new CSTNode('template_content', ctx.source, first.startOffset, last.endOffset, first.startPosition, last.endPosition);
227
+ mergedNode.parent = template;
228
+ merged.push(mergedNode);
229
+ i = end;
230
+ }
231
+ else {
232
+ merged.push(child);
233
+ i++;
234
+ }
235
+ }
236
+ else {
237
+ merged.push(child);
238
+ i++;
239
+ }
240
+ }
241
+ template.children = merged;
242
+ }
243
+ /**
244
+ * Gather tokens on the current line as template content.
245
+ * Recognizes {! ... } as template expression breaks.
246
+ * Everything else (including inter-token whitespace) becomes template_content.
247
+ * Uses source-level offsets so whitespace between tokens is preserved.
248
+ */
249
+ function gatherTemplateContentLine(ctx, parent, initialOffset, initialPos) {
250
+ // Track the source range of content before/after template expressions.
251
+ // If initialOffset is provided, use it (captures whitespace after |).
252
+ // Otherwise start from the current token position.
253
+ let contentStartOffset = initialOffset ?? ctx.peekOffset();
254
+ let contentStartPos = initialPos ?? ctx.peek().start;
255
+ let lastConsumedEndOffset = contentStartOffset;
256
+ let lastConsumedEndPos = contentStartPos;
257
+ while (!isAtEnd(ctx)) {
258
+ const tok = ctx.peek();
259
+ if (tok.kind === TokenKind.NEWLINE ||
260
+ tok.kind === TokenKind.DEDENT ||
261
+ tok.kind === TokenKind.INDENT ||
262
+ tok.kind === TokenKind.EOF) {
263
+ break;
264
+ }
265
+ // Template expression start
266
+ if (tok.kind === TokenKind.TEMPLATE_EXPR_START) {
267
+ // Flush accumulated content up to the {!
268
+ const exprOffset = ctx.peekOffset();
269
+ if (exprOffset > contentStartOffset) {
270
+ parent.appendChild(new CSTNode('template_content', ctx.source, contentStartOffset, exprOffset, contentStartPos, tok.start));
271
+ }
272
+ // Parse template expression
273
+ const exprNode = parseTemplateExpression(ctx);
274
+ parent.appendChild(exprNode);
275
+ // Content after } continues from the end of the expression node
276
+ // (not from the next token — that would skip whitespace between } and next content)
277
+ contentStartOffset = exprNode.endOffset;
278
+ contentStartPos = exprNode.endPosition;
279
+ lastConsumedEndOffset = exprNode.endOffset;
280
+ lastConsumedEndPos = exprNode.endPosition;
281
+ continue;
282
+ }
283
+ // Track end of this token for accurate content span
284
+ const tokOffset = ctx.peekOffset();
285
+ lastConsumedEndOffset = tokOffset + tok.text.length;
286
+ lastConsumedEndPos = tok.end;
287
+ ctx.consume();
288
+ }
289
+ // Flush remaining content — use end of last consumed token (not next token offset,
290
+ // which would include blank lines between this content and the next line)
291
+ if (lastConsumedEndOffset > contentStartOffset) {
292
+ parent.appendChild(new CSTNode('template_content', ctx.source, contentStartOffset, lastConsumedEndOffset, contentStartPos, lastConsumedEndPos));
293
+ }
294
+ }
295
+ function parseTemplateExpression(ctx) {
296
+ const startTok = ctx.peek();
297
+ const node = ctx.startNode('template_expression');
298
+ ctx.addAnonymousChild(node, ctx.consume()); // {!
299
+ const expr = parseExpression(ctx, 0);
300
+ if (expr) {
301
+ node.appendChild(wrapExpression(ctx, expr), 'expression');
302
+ }
303
+ else {
304
+ // Empty template expression {!} → ERROR for missing expression
305
+ node.appendChild(makeEmptyError(ctx));
306
+ }
307
+ // Consume any extra tokens before } (e.g., unclosed {!@var.name world)
308
+ if (ctx.peekKind() !== TokenKind.RBRACE && !ctx.isAtSyncPoint()) {
309
+ const err = synchronize(ctx);
310
+ if (err)
311
+ node.appendChild(err);
312
+ }
313
+ if (ctx.peekKind() === TokenKind.RBRACE) {
314
+ ctx.addAnonymousChild(node, ctx.consume()); // }
315
+ }
316
+ else {
317
+ // Unclosed template expression → MISSING }
318
+ node.appendChild(makeMissing(ctx, '}'));
319
+ }
320
+ ctx.finishNode(node, startTok);
321
+ return node;
322
+ }
323
+ //# sourceMappingURL=parse-templates.js.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Recursive descent parser for AgentScript.
3
+ *
4
+ * Core invariant: NEWLINE and DEDENT are unconditional synchronization points.
5
+ * Every parse function that encounters an unexpected token calls synchronize()
6
+ * which skips to the next NEWLINE/DEDENT/EOF.
7
+ */
8
+ import { TokenKind, type Token } from './token.js';
9
+ import { CSTNode } from './cst-node.js';
10
+ /**
11
+ * Token consumption — peek, advance, and query the token stream.
12
+ */
13
+ export interface TokenStream {
14
+ source: string;
15
+ peek(): Token;
16
+ peekAt(offset: number): Token;
17
+ peekAtIndex(idx: number): Token;
18
+ peekKind(): TokenKind;
19
+ consume(): Token;
20
+ consumeKind<K extends TokenKind>(kind: K): Token<K>;
21
+ currentOffset(): number;
22
+ peekOffset(): number;
23
+ isAtSyncPoint(): boolean;
24
+ }
25
+ /**
26
+ * CST node construction — create, populate, and finalize nodes.
27
+ */
28
+ export interface NodeBuilder {
29
+ consumeNamed(type: string): CSTNode;
30
+ startNode(type: string): CSTNode;
31
+ startNodeAt(type: string, existingChild: CSTNode): CSTNode;
32
+ finishNode(node: CSTNode, startTok: Token): void;
33
+ addAnonymousChild(parent: CSTNode, token: Token): void;
34
+ }
35
+ /**
36
+ * Combined interface used by expression parser to access parser state.
37
+ * Avoids circular dependency between parser.ts and expressions.ts.
38
+ */
39
+ export interface ParserContext extends TokenStream, NodeBuilder {
40
+ }
41
+ export declare class Parser implements ParserContext {
42
+ source: string;
43
+ private tokens;
44
+ private pos;
45
+ private _eof;
46
+ constructor(source: string);
47
+ parse(): CSTNode;
48
+ peek(): Token;
49
+ peekAt(offset: number): Token;
50
+ peekAtIndex(idx: number): Token;
51
+ peekKind(): TokenKind;
52
+ consume(): Token;
53
+ consumeKind<K extends TokenKind>(kind: K): Token<K>;
54
+ consumeNamed(type: string): CSTNode;
55
+ currentOffset(): number;
56
+ peekOffset(): number;
57
+ isAtSyncPoint(): boolean;
58
+ startNode(type: string): CSTNode;
59
+ startNodeAt(type: string, existingChild: CSTNode): CSTNode;
60
+ finishNode(_node: CSTNode, _startTok: Token): void;
61
+ addAnonymousChild(parent: CSTNode, token: Token): void;
62
+ private parseSourceFile;
63
+ private eofToken;
64
+ }
65
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1,163 @@
1
+ /*
2
+ * Copyright (c) 2026, Salesforce, Inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ * For full license text, see the LICENSE file in the repo root or https://www.apache.org/licenses/LICENSE-2.0
6
+ */
7
+ /**
8
+ * Recursive descent parser for AgentScript.
9
+ *
10
+ * Core invariant: NEWLINE and DEDENT are unconditional synchronization points.
11
+ * Every parse function that encounters an unexpected token calls synchronize()
12
+ * which skips to the next NEWLINE/DEDENT/EOF.
13
+ */
14
+ import { isTokenKind, TokenKind } from './token.js';
15
+ import { Lexer } from './lexer.js';
16
+ import { CSTNode } from './cst-node.js';
17
+ import { isSyncPoint } from './errors.js';
18
+ import { synchronize, skipNewlines, consumeCommentsAndSkipNewlines, isAtEnd, } from './recovery.js';
19
+ import { parseMappingOrExpression } from './parse-mapping.js';
20
+ import { parseSequence } from './parse-sequence.js';
21
+ import invariant from 'tiny-invariant';
22
+ export class Parser {
23
+ source;
24
+ tokens;
25
+ pos = 0;
26
+ _eof;
27
+ constructor(source) {
28
+ this.source = source;
29
+ const lexer = new Lexer(source);
30
+ this.tokens = lexer.tokenize();
31
+ }
32
+ parse() {
33
+ const root = this.parseSourceFile();
34
+ return root;
35
+ }
36
+ // --- ParserContext implementation ---
37
+ peek() {
38
+ return this.peekAt(0);
39
+ }
40
+ peekAt(offset) {
41
+ // n.b. (Allen): Because this is called so frequently, these invariants cause significant runtime overhead.
42
+ // invariant(this.pos + offset >= 0, 'peekAt too small');
43
+ // invariant(this.pos + offset <= this.tokens.length, 'peekAt too large');
44
+ return this.peekAtIndex(this.pos + offset);
45
+ }
46
+ peekAtIndex(idx) {
47
+ return this.tokens[idx] ?? this.eofToken();
48
+ }
49
+ peekKind() {
50
+ return this.peek().kind;
51
+ }
52
+ consume() {
53
+ const tok = this.peek();
54
+ this.pos++;
55
+ return tok;
56
+ }
57
+ consumeKind(kind) {
58
+ const tok = this.peek();
59
+ invariant(isTokenKind(tok, kind), `Expected token kind ${kind} but got ${tok.kind}`);
60
+ this.pos++;
61
+ return tok;
62
+ }
63
+ consumeNamed(type) {
64
+ const tok = this.consume();
65
+ const offset = tok.startOffset;
66
+ return new CSTNode(type, this.source, offset, offset + tok.text.length, tok.start, tok.end);
67
+ }
68
+ currentOffset() {
69
+ const idx = this.pos > 0 ? this.pos - 1 : 0;
70
+ return this.peekAtIndex(idx).startOffset;
71
+ }
72
+ peekOffset() {
73
+ return this.peek().startOffset;
74
+ }
75
+ isAtSyncPoint() {
76
+ return isSyncPoint(this.peekKind());
77
+ }
78
+ startNode(type) {
79
+ const tok = this.peek();
80
+ const offset = tok.startOffset;
81
+ return new CSTNode(type, this.source, offset, offset, tok.start, tok.end);
82
+ }
83
+ startNodeAt(type, existingChild) {
84
+ return new CSTNode(type, this.source, existingChild.startOffset, existingChild.endOffset, existingChild.startPosition, existingChild.endPosition);
85
+ }
86
+ finishNode(_node, _startTok) {
87
+ // No-op: appendChild() tracks end position incrementally.
88
+ }
89
+ addAnonymousChild(parent, token) {
90
+ const offset = token.startOffset;
91
+ const child = new CSTNode(token.text, this.source, offset, offset + token.text.length, token.start, token.end, false);
92
+ parent.appendChild(child);
93
+ }
94
+ // --- Top-level parsing ---
95
+ parseSourceFile() {
96
+ const node = this.startNode('source_file');
97
+ // Skip leading newlines and indentation (handles template literals with leading whitespace)
98
+ skipNewlines(this);
99
+ if (this.peekKind() === TokenKind.INDENT) {
100
+ this.consume();
101
+ }
102
+ // Consume leading comments at source_file level (tree-sitter treats them as extras)
103
+ consumeCommentsAndSkipNewlines(this, node);
104
+ // Determine what kind of source file this is
105
+ if (this.peekKind() === TokenKind.DASH_SPACE) {
106
+ // Sequence
107
+ node.appendChild(parseSequence(this));
108
+ }
109
+ else {
110
+ // Mapping or expression
111
+ // n.b. (Allen): Originally we didn't permit expressions at the top level, but
112
+ // we did that to make testing easier in tree-sitter so I suppose
113
+ // we can just make this a feature of the language.
114
+ const content = parseMappingOrExpression(this, _ctx => parseSequence(_ctx));
115
+ if (content)
116
+ node.appendChild(content);
117
+ }
118
+ // Consume trailing comments at source_file level
119
+ consumeCommentsAndSkipNewlines(this, node);
120
+ // Catch-all: if there are unconsumed tokens, wrap them in ERROR nodes.
121
+ // This ensures every byte of source is represented in the CST.
122
+ while (!isAtEnd(this)) {
123
+ if (this.peekKind() === TokenKind.NEWLINE ||
124
+ this.peekKind() === TokenKind.DEDENT) {
125
+ this.consume();
126
+ continue;
127
+ }
128
+ if (this.peekKind() === TokenKind.COMMENT) {
129
+ node.appendChild(this.consumeNamed('comment'));
130
+ continue;
131
+ }
132
+ const err = synchronize(this);
133
+ if (err) {
134
+ node.appendChild(err);
135
+ }
136
+ else {
137
+ // Consume one token to guarantee progress
138
+ this.consume();
139
+ }
140
+ }
141
+ // Root node must span entire source (matches tree-sitter invariant)
142
+ node.startOffset = 0;
143
+ node.startPosition = { row: 0, column: 0 };
144
+ node.endOffset = this.source.length;
145
+ node.endPosition = this.eofToken().end;
146
+ return node;
147
+ }
148
+ eofToken() {
149
+ if (!this._eof) {
150
+ const lastToken = this.tokens[this.tokens.length - 1];
151
+ const pos = lastToken ? lastToken.end : { row: 0, column: 0 };
152
+ this._eof = {
153
+ kind: TokenKind.EOF,
154
+ text: '',
155
+ start: pos,
156
+ end: pos,
157
+ startOffset: this.source.length,
158
+ };
159
+ }
160
+ return this._eof;
161
+ }
162
+ }
163
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Recovery and utility functions extracted from parser.ts.
3
+ *
4
+ * All functions take a ParserContext as their first argument,
5
+ * following the free-function pattern established by expressions.ts.
6
+ */
7
+ import { TokenKind } from './token.js';
8
+ import { CSTNode } from './cst-node.js';
9
+ import type { ParserContext } from './parser.js';
10
+ /** Create an empty ERROR node at the current position. */
11
+ export declare function makeEmptyError(ctx: ParserContext): CSTNode;
12
+ /** Insert a missing target: `target: (expression (atom (ERROR)))` */
13
+ export declare function addMissingTarget(ctx: ParserContext, node: CSTNode): void;
14
+ /** Create a MISSING node — an expected token/node that wasn't found in source. */
15
+ export declare function makeMissing(ctx: ParserContext, type: string): CSTNode;
16
+ /**
17
+ * Parse a standalone else/elif/for (without a preceding if, or unsupported).
18
+ * Wraps the entire block in an ERROR node, preserving parsed statements inside.
19
+ *
20
+ * @param parseProcedure - callback to parse procedure bodies, avoiding circular
21
+ * dependency with parse-statements.ts
22
+ */
23
+ export declare function parseOrphanBlock(ctx: ParserContext, parseProcedure: (ctx: ParserContext) => CSTNode): CSTNode;
24
+ /**
25
+ * Consume any leftover tokens in an indented block (before DEDENT) as ERROR
26
+ * nodes. Prevents cascading failures when parseBlockValue() only partially
27
+ * consumes the block content (e.g., unquoted multi-word text).
28
+ */
29
+ export declare function recoverToBlockEnd(ctx: ParserContext, parent: CSTNode): void;
30
+ /**
31
+ * Synchronize: skip tokens until a stopping condition is met.
32
+ * Returns an ERROR node wrapping the skipped content, or null if
33
+ * nothing was consumed.
34
+ *
35
+ * @param extraStop - optional predicate for additional stop conditions
36
+ * beyond the default sync points (NEWLINE/DEDENT/EOF)
37
+ */
38
+ export declare function synchronizeUntil(ctx: ParserContext, extraStop?: (kind: TokenKind, row: number) => boolean): CSTNode | null;
39
+ /** Skip tokens on the given row until a sync point, INDENT, or COLON. */
40
+ export declare function synchronizeRowUntilColon(ctx: ParserContext, row: number): CSTNode | null;
41
+ /** Skip tokens on the given row until a sync point or INDENT. */
42
+ export declare function synchronizeRow(ctx: ParserContext, row: number): CSTNode | null;
43
+ /** Skip tokens until the next sync point (NEWLINE/DEDENT/EOF). */
44
+ export declare function synchronize(ctx: ParserContext): CSTNode | null;
45
+ export declare function skipNewlines(ctx: ParserContext): void;
46
+ /** Consume comment and newline tokens and attach to parent node. */
47
+ export declare function consumeCommentsAndSkipNewlines(ctx: ParserContext, parent: CSTNode): void;
48
+ export declare function isAtEnd(ctx: ParserContext): boolean;
49
+ /** Check if from current position, there are only comments, newlines, and then EOF/DEDENT. */
50
+ export declare function isTrailingCommentOnly(ctx: ParserContext): boolean;
51
+ //# sourceMappingURL=recovery.d.ts.map