@promptscript/cli 1.1.0 → 1.3.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/CHANGELOG.md +19 -0
- package/index.js +544 -183
- package/package.json +1 -1
- package/skills/promptscript/SKILL.md +25 -16
- package/src/commands/compile.d.ts.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0](https://github.com/mrwogu/promptscript/compare/v1.2.0...v1.3.0) (2026-03-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **formatters:** add mixed models support per agent/droid ([939f75a](https://github.com/mrwogu/promptscript/commit/939f75ac4d8beb2e823007826c2e266ab5c7380b))
|
|
14
|
+
|
|
15
|
+
## [1.2.0](https://github.com/mrwogu/promptscript/compare/v1.1.0...v1.2.0) (2026-03-11)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **formatters:** add Factory AI droids support (.factory/droids/) ([#94](https://github.com/mrwogu/promptscript/issues/94)) ([e7f9292](https://github.com/mrwogu/promptscript/commit/e7f929273254738b2de5334f26136c8374aedc7b))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Bug Fixes
|
|
24
|
+
|
|
25
|
+
* **cli:** handle skill resource files without PromptScript markers ([#92](https://github.com/mrwogu/promptscript/issues/92)) ([3153498](https://github.com/mrwogu/promptscript/commit/315349865335cc1013b5c60d5c418885bf6467f8))
|
|
26
|
+
|
|
8
27
|
## [1.1.0](https://github.com/mrwogu/promptscript/compare/v1.0.0...v1.1.0) (2026-03-11)
|
|
9
28
|
|
|
10
29
|
|
package/index.js
CHANGED
|
@@ -3331,6 +3331,7 @@ var MarkdownInstructionFormatter = class extends BaseFormatter {
|
|
|
3331
3331
|
this.addSection(sections, this.postWork(ast, renderer));
|
|
3332
3332
|
this.addSection(sections, this.documentation(ast, renderer));
|
|
3333
3333
|
this.addSection(sections, this.diagrams(ast, renderer));
|
|
3334
|
+
this.addSection(sections, this.knowledgeContent(ast, renderer));
|
|
3334
3335
|
this.addSection(sections, this.restrictions(ast, renderer));
|
|
3335
3336
|
}
|
|
3336
3337
|
addSection(sections, content) {
|
|
@@ -3510,6 +3511,38 @@ var MarkdownInstructionFormatter = class extends BaseFormatter {
|
|
|
3510
3511
|
const content = renderer.renderList(items);
|
|
3511
3512
|
return renderer.renderSection("Diagrams", content) + "\n";
|
|
3512
3513
|
}
|
|
3514
|
+
/**
|
|
3515
|
+
* Render remaining @knowledge text content that isn't consumed by other sections.
|
|
3516
|
+
* Strips "## Development Commands" and "## Post-Work Verification" sub-sections
|
|
3517
|
+
* since those are already rendered by commands() and postWork().
|
|
3518
|
+
*/
|
|
3519
|
+
knowledgeContent(ast, _renderer) {
|
|
3520
|
+
const knowledge = this.findBlock(ast, "knowledge");
|
|
3521
|
+
if (!knowledge) return null;
|
|
3522
|
+
const text = this.extractText(knowledge.content);
|
|
3523
|
+
if (!text) return null;
|
|
3524
|
+
const consumedHeaders = ["## Development Commands", "## Post-Work Verification"];
|
|
3525
|
+
let remaining = text;
|
|
3526
|
+
for (const header of consumedHeaders) {
|
|
3527
|
+
const headerIndex = remaining.indexOf(header);
|
|
3528
|
+
if (headerIndex === -1) continue;
|
|
3529
|
+
const afterHeader = remaining.indexOf("\n", headerIndex);
|
|
3530
|
+
if (afterHeader === -1) {
|
|
3531
|
+
remaining = remaining.substring(0, headerIndex).trimEnd();
|
|
3532
|
+
continue;
|
|
3533
|
+
}
|
|
3534
|
+
const nextSection = remaining.indexOf("\n## ", afterHeader);
|
|
3535
|
+
if (nextSection === -1) {
|
|
3536
|
+
remaining = remaining.substring(0, headerIndex).trimEnd();
|
|
3537
|
+
} else {
|
|
3538
|
+
remaining = remaining.substring(0, headerIndex) + remaining.substring(nextSection + 1);
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
remaining = remaining.trim();
|
|
3542
|
+
if (!remaining) return null;
|
|
3543
|
+
const normalizedContent = this.stripAllIndent(remaining);
|
|
3544
|
+
return normalizedContent + "\n";
|
|
3545
|
+
}
|
|
3513
3546
|
restrictions(ast, renderer) {
|
|
3514
3547
|
const block = this.findBlock(ast, "restrictions");
|
|
3515
3548
|
if (!block) return null;
|
|
@@ -4096,6 +4129,7 @@ var GitHubFormatter = class extends BaseFormatter {
|
|
|
4096
4129
|
description,
|
|
4097
4130
|
tools: this.parseToolsArray(obj["tools"]),
|
|
4098
4131
|
model: obj["model"] ? this.valueToString(obj["model"]) : void 0,
|
|
4132
|
+
specModel: obj["specModel"] ? this.valueToString(obj["specModel"]) : void 0,
|
|
4099
4133
|
handoffs: handoffs.length > 0 ? handoffs : void 0,
|
|
4100
4134
|
content: obj["content"] ? this.valueToString(obj["content"]) : ""
|
|
4101
4135
|
});
|
|
@@ -4187,6 +4221,10 @@ var GitHubFormatter = class extends BaseFormatter {
|
|
|
4187
4221
|
if (mappedModel) {
|
|
4188
4222
|
lines.push(`model: ${mappedModel}`);
|
|
4189
4223
|
}
|
|
4224
|
+
const mappedSpecModel = this.mapModelName(config.specModel);
|
|
4225
|
+
if (mappedSpecModel) {
|
|
4226
|
+
lines.push(`specModel: ${mappedSpecModel}`);
|
|
4227
|
+
}
|
|
4190
4228
|
if (config.handoffs && config.handoffs.length > 0) {
|
|
4191
4229
|
lines.push(...this.renderHandoffsYaml(config.handoffs));
|
|
4192
4230
|
}
|
|
@@ -6427,7 +6465,7 @@ var FACTORY_VERSIONS = {
|
|
|
6427
6465
|
},
|
|
6428
6466
|
full: {
|
|
6429
6467
|
name: "full",
|
|
6430
|
-
description: "Multifile + additional
|
|
6468
|
+
description: "Multifile + droids + additional supporting files",
|
|
6431
6469
|
outputPath: "AGENTS.md"
|
|
6432
6470
|
}
|
|
6433
6471
|
};
|
|
@@ -6441,7 +6479,7 @@ var FactoryFormatter = class extends MarkdownInstructionFormatter {
|
|
|
6441
6479
|
mainFileHeader: "# AGENTS.md",
|
|
6442
6480
|
dotDir: ".factory",
|
|
6443
6481
|
skillFileName: "SKILL.md",
|
|
6444
|
-
hasAgents:
|
|
6482
|
+
hasAgents: true,
|
|
6445
6483
|
hasCommands: true,
|
|
6446
6484
|
hasSkills: true,
|
|
6447
6485
|
skillsInMultifile: true,
|
|
@@ -6584,6 +6622,87 @@ var FactoryFormatter = class extends MarkdownInstructionFormatter {
|
|
|
6584
6622
|
};
|
|
6585
6623
|
}
|
|
6586
6624
|
// ============================================================
|
|
6625
|
+
// Droid File Generation (Factory-specific)
|
|
6626
|
+
// ============================================================
|
|
6627
|
+
extractAgents(ast) {
|
|
6628
|
+
const agentsBlock = this.findBlock(ast, "agents");
|
|
6629
|
+
if (!agentsBlock) return [];
|
|
6630
|
+
const droids = [];
|
|
6631
|
+
const props = this.getProps(agentsBlock.content);
|
|
6632
|
+
for (const [name, value] of Object.entries(props)) {
|
|
6633
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
6634
|
+
const obj = value;
|
|
6635
|
+
const description = obj["description"] ? this.valueToString(obj["description"]) : "";
|
|
6636
|
+
if (!description) continue;
|
|
6637
|
+
droids.push({
|
|
6638
|
+
name: name.replace(/\./g, "-"),
|
|
6639
|
+
description,
|
|
6640
|
+
content: obj["content"] ? this.valueToString(obj["content"]) : "",
|
|
6641
|
+
model: obj["model"] ? this.valueToString(obj["model"]) : void 0,
|
|
6642
|
+
reasoningEffort: this.parseReasoningEffort(obj["reasoningEffort"]),
|
|
6643
|
+
specModel: obj["specModel"] ? this.valueToString(obj["specModel"]) : void 0,
|
|
6644
|
+
specReasoningEffort: this.parseReasoningEffort(obj["specReasoningEffort"]),
|
|
6645
|
+
tools: this.parseDroidTools(obj["tools"])
|
|
6646
|
+
});
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
return droids;
|
|
6650
|
+
}
|
|
6651
|
+
generateAgentFile(config) {
|
|
6652
|
+
const droidConfig = config;
|
|
6653
|
+
const lines = [];
|
|
6654
|
+
lines.push("---");
|
|
6655
|
+
lines.push(`name: ${droidConfig.name}`);
|
|
6656
|
+
if (droidConfig.description) {
|
|
6657
|
+
lines.push(`description: ${this.yamlString(droidConfig.description)}`);
|
|
6658
|
+
}
|
|
6659
|
+
if (droidConfig.model) {
|
|
6660
|
+
lines.push(`model: ${droidConfig.model}`);
|
|
6661
|
+
}
|
|
6662
|
+
if (droidConfig.reasoningEffort) {
|
|
6663
|
+
lines.push(`reasoningEffort: ${droidConfig.reasoningEffort}`);
|
|
6664
|
+
}
|
|
6665
|
+
if (droidConfig.specModel) {
|
|
6666
|
+
lines.push(`specModel: ${droidConfig.specModel}`);
|
|
6667
|
+
}
|
|
6668
|
+
if (droidConfig.specReasoningEffort) {
|
|
6669
|
+
lines.push(`specReasoningEffort: ${droidConfig.specReasoningEffort}`);
|
|
6670
|
+
}
|
|
6671
|
+
if (droidConfig.tools) {
|
|
6672
|
+
if (typeof droidConfig.tools === "string") {
|
|
6673
|
+
lines.push(`tools: ${droidConfig.tools}`);
|
|
6674
|
+
} else if (Array.isArray(droidConfig.tools) && droidConfig.tools.length > 0) {
|
|
6675
|
+
const toolsArray = droidConfig.tools.map((t) => `"${t}"`).join(", ");
|
|
6676
|
+
lines.push(`tools: [${toolsArray}]`);
|
|
6677
|
+
}
|
|
6678
|
+
}
|
|
6679
|
+
lines.push("---");
|
|
6680
|
+
lines.push("");
|
|
6681
|
+
if (droidConfig.content) {
|
|
6682
|
+
const dedentedContent = this.dedent(droidConfig.content);
|
|
6683
|
+
lines.push(dedentedContent);
|
|
6684
|
+
}
|
|
6685
|
+
return {
|
|
6686
|
+
path: `.factory/droids/${droidConfig.name}.md`,
|
|
6687
|
+
content: lines.join("\n") + "\n"
|
|
6688
|
+
};
|
|
6689
|
+
}
|
|
6690
|
+
parseReasoningEffort(value) {
|
|
6691
|
+
if (!value) return void 0;
|
|
6692
|
+
const str = this.valueToString(value);
|
|
6693
|
+
const valid = ["low", "medium", "high"];
|
|
6694
|
+
return valid.includes(str) ? str : void 0;
|
|
6695
|
+
}
|
|
6696
|
+
parseDroidTools(value) {
|
|
6697
|
+
if (!value) return void 0;
|
|
6698
|
+
if (typeof value === "string") return value;
|
|
6699
|
+
if (Array.isArray(value)) {
|
|
6700
|
+
const arr = value.map((v) => this.valueToString(v)).filter((s) => s.length > 0);
|
|
6701
|
+
return arr.length > 0 ? arr : void 0;
|
|
6702
|
+
}
|
|
6703
|
+
return this.valueToString(value);
|
|
6704
|
+
}
|
|
6705
|
+
// ============================================================
|
|
6587
6706
|
// Handoff Extraction (Factory-specific)
|
|
6588
6707
|
// ============================================================
|
|
6589
6708
|
extractHandoffs(value) {
|
|
@@ -19200,16 +19319,170 @@ function parseSkillMd(content) {
|
|
|
19200
19319
|
}
|
|
19201
19320
|
var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
19202
19321
|
"SKILL.md",
|
|
19322
|
+
".skillignore",
|
|
19203
19323
|
".DS_Store",
|
|
19204
19324
|
"Thumbs.db",
|
|
19205
19325
|
".gitignore",
|
|
19206
19326
|
".gitkeep",
|
|
19207
19327
|
".npmrc",
|
|
19328
|
+
".npmignore",
|
|
19208
19329
|
".env",
|
|
19209
19330
|
".env.local",
|
|
19210
|
-
".env.production"
|
|
19331
|
+
".env.production",
|
|
19332
|
+
".editorconfig",
|
|
19333
|
+
".prettierrc",
|
|
19334
|
+
".prettierrc.json",
|
|
19335
|
+
".prettierrc.yaml",
|
|
19336
|
+
".prettierrc.yml",
|
|
19337
|
+
".prettierignore",
|
|
19338
|
+
".eslintrc",
|
|
19339
|
+
".eslintrc.js",
|
|
19340
|
+
".eslintrc.cjs",
|
|
19341
|
+
".eslintrc.json",
|
|
19342
|
+
".eslintrc.yaml",
|
|
19343
|
+
".eslintrc.yml",
|
|
19344
|
+
"eslint.config.js",
|
|
19345
|
+
"eslint.config.cjs",
|
|
19346
|
+
"eslint.config.mjs",
|
|
19347
|
+
"eslint.base.config.cjs",
|
|
19348
|
+
".release-please-manifest.json",
|
|
19349
|
+
"release-please-config.json",
|
|
19350
|
+
"package-lock.json",
|
|
19351
|
+
"pnpm-lock.yaml",
|
|
19352
|
+
"pnpm-workspace.yaml",
|
|
19353
|
+
"yarn.lock",
|
|
19354
|
+
"tsconfig.json",
|
|
19355
|
+
"tsconfig.base.json",
|
|
19356
|
+
"tsconfig.build.json",
|
|
19357
|
+
"tsconfig.spec.json",
|
|
19358
|
+
"jest.config.ts",
|
|
19359
|
+
"jest.config.js",
|
|
19360
|
+
"vitest.config.ts",
|
|
19361
|
+
"vitest.config.js",
|
|
19362
|
+
"vite.config.ts",
|
|
19363
|
+
"vite.config.js",
|
|
19364
|
+
"nx.json",
|
|
19365
|
+
"project.json",
|
|
19366
|
+
"package.json",
|
|
19367
|
+
"Makefile",
|
|
19368
|
+
"Dockerfile",
|
|
19369
|
+
"docker-compose.yml",
|
|
19370
|
+
"docker-compose.yaml",
|
|
19371
|
+
".dockerignore",
|
|
19372
|
+
"LICENSE",
|
|
19373
|
+
"LICENSE.md",
|
|
19374
|
+
"CHANGELOG.md",
|
|
19375
|
+
"CONTRIBUTING.md",
|
|
19376
|
+
"CODE_OF_CONDUCT.md",
|
|
19377
|
+
"ROADMAP.md"
|
|
19378
|
+
]);
|
|
19379
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
19380
|
+
"node_modules",
|
|
19381
|
+
"__pycache__",
|
|
19382
|
+
".git",
|
|
19383
|
+
".svn",
|
|
19384
|
+
".github",
|
|
19385
|
+
".husky",
|
|
19386
|
+
".vscode",
|
|
19387
|
+
".idea",
|
|
19388
|
+
".verdaccio",
|
|
19389
|
+
".nx",
|
|
19390
|
+
".cache",
|
|
19391
|
+
".turbo",
|
|
19392
|
+
"dist",
|
|
19393
|
+
"build",
|
|
19394
|
+
"out",
|
|
19395
|
+
"coverage",
|
|
19396
|
+
"tmp",
|
|
19397
|
+
".tmp",
|
|
19398
|
+
"e2e",
|
|
19399
|
+
"__tests__",
|
|
19400
|
+
"__mocks__",
|
|
19401
|
+
"__fixtures__",
|
|
19402
|
+
"test",
|
|
19403
|
+
"tests",
|
|
19404
|
+
"spec",
|
|
19405
|
+
"fixtures"
|
|
19211
19406
|
]);
|
|
19212
|
-
|
|
19407
|
+
function globToRegex(pattern) {
|
|
19408
|
+
const isDirPattern = pattern.endsWith("/");
|
|
19409
|
+
const cleanPattern = isDirPattern ? pattern.slice(0, -1) : pattern;
|
|
19410
|
+
const matchPath = cleanPattern.includes("/");
|
|
19411
|
+
let regexStr = "";
|
|
19412
|
+
let i = 0;
|
|
19413
|
+
while (i < cleanPattern.length) {
|
|
19414
|
+
const char = cleanPattern[i];
|
|
19415
|
+
if (char === "*") {
|
|
19416
|
+
if (cleanPattern[i + 1] === "*") {
|
|
19417
|
+
if (cleanPattern[i + 2] === "/") {
|
|
19418
|
+
regexStr += "(?:.+/)?";
|
|
19419
|
+
i += 3;
|
|
19420
|
+
} else {
|
|
19421
|
+
regexStr += ".*";
|
|
19422
|
+
i += 2;
|
|
19423
|
+
}
|
|
19424
|
+
} else {
|
|
19425
|
+
regexStr += "[^/]*";
|
|
19426
|
+
i++;
|
|
19427
|
+
}
|
|
19428
|
+
} else if (char === "?") {
|
|
19429
|
+
regexStr += "[^/]";
|
|
19430
|
+
i++;
|
|
19431
|
+
} else if (char === "[") {
|
|
19432
|
+
const closeIdx = cleanPattern.indexOf("]", i + 1);
|
|
19433
|
+
if (closeIdx > i) {
|
|
19434
|
+
regexStr += cleanPattern.slice(i, closeIdx + 1);
|
|
19435
|
+
i = closeIdx + 1;
|
|
19436
|
+
} else {
|
|
19437
|
+
regexStr += "\\[";
|
|
19438
|
+
i++;
|
|
19439
|
+
}
|
|
19440
|
+
} else if (".+^${}()|\\".includes(char)) {
|
|
19441
|
+
regexStr += "\\" + char;
|
|
19442
|
+
i++;
|
|
19443
|
+
} else {
|
|
19444
|
+
regexStr += char;
|
|
19445
|
+
i++;
|
|
19446
|
+
}
|
|
19447
|
+
}
|
|
19448
|
+
if (isDirPattern) {
|
|
19449
|
+
return { regex: new RegExp(`^${regexStr}(?:/|$)`), matchPath: true };
|
|
19450
|
+
}
|
|
19451
|
+
return { regex: new RegExp(`^${regexStr}$`), matchPath };
|
|
19452
|
+
}
|
|
19453
|
+
function parseSkillIgnore(content) {
|
|
19454
|
+
const patterns = [];
|
|
19455
|
+
for (const rawLine of content.split("\n")) {
|
|
19456
|
+
const line = rawLine.trim();
|
|
19457
|
+
if (!line || line.startsWith("#")) continue;
|
|
19458
|
+
const negated = line.startsWith("!");
|
|
19459
|
+
const pattern = negated ? line.slice(1) : line;
|
|
19460
|
+
if (!pattern) continue;
|
|
19461
|
+
const { regex, matchPath } = globToRegex(pattern);
|
|
19462
|
+
patterns.push({ regex, matchPath, negated });
|
|
19463
|
+
}
|
|
19464
|
+
return { patterns };
|
|
19465
|
+
}
|
|
19466
|
+
function isIgnoredByRules(relPath, rules) {
|
|
19467
|
+
const basename3 = relPath.split("/").pop() ?? relPath;
|
|
19468
|
+
let ignored = false;
|
|
19469
|
+
for (const { regex, matchPath, negated } of rules.patterns) {
|
|
19470
|
+
const target = matchPath ? relPath : basename3;
|
|
19471
|
+
if (regex.test(target)) {
|
|
19472
|
+
ignored = !negated;
|
|
19473
|
+
}
|
|
19474
|
+
}
|
|
19475
|
+
return ignored;
|
|
19476
|
+
}
|
|
19477
|
+
async function loadSkillIgnore(skillDir) {
|
|
19478
|
+
const ignorePath = resolve4(skillDir, ".skillignore");
|
|
19479
|
+
try {
|
|
19480
|
+
const content = await readFile6(ignorePath, "utf-8");
|
|
19481
|
+
return parseSkillIgnore(content);
|
|
19482
|
+
} catch {
|
|
19483
|
+
return null;
|
|
19484
|
+
}
|
|
19485
|
+
}
|
|
19213
19486
|
var MAX_RESOURCE_SIZE = 1048576;
|
|
19214
19487
|
var MAX_TOTAL_RESOURCE_SIZE = 10485760;
|
|
19215
19488
|
var MAX_RESOURCE_COUNT = 100;
|
|
@@ -19229,6 +19502,7 @@ var noopLogger2 = {
|
|
|
19229
19502
|
}
|
|
19230
19503
|
};
|
|
19231
19504
|
async function discoverSkillResources(skillDir, logger = noopLogger2) {
|
|
19505
|
+
const ignoreRules = await loadSkillIgnore(skillDir);
|
|
19232
19506
|
const entries = await readdir2(skillDir, { recursive: true, withFileTypes: true });
|
|
19233
19507
|
const resources = [];
|
|
19234
19508
|
let totalSize = 0;
|
|
@@ -19246,8 +19520,13 @@ async function discoverSkillResources(skillDir, logger = noopLogger2) {
|
|
|
19246
19520
|
if (SKIP_FILES.has(entry.name)) continue;
|
|
19247
19521
|
const fullPath = resolve4(entry.parentPath, entry.name);
|
|
19248
19522
|
const relPath = relative(skillDir, fullPath);
|
|
19523
|
+
const relPathNormalized = relPath.split(sep2).join("/");
|
|
19249
19524
|
const pathSegments = relPath.split(sep2);
|
|
19250
19525
|
if (pathSegments.some((s) => SKIP_DIRS.has(s))) continue;
|
|
19526
|
+
if (ignoreRules && isIgnoredByRules(relPathNormalized, ignoreRules)) {
|
|
19527
|
+
logger.debug(`Skipping resource ignored by .skillignore: ${relPath}`);
|
|
19528
|
+
continue;
|
|
19529
|
+
}
|
|
19251
19530
|
if (!isSafeRelativePath(relPath)) {
|
|
19252
19531
|
logger.verbose(`Skipping resource with unsafe path: ${relPath}`);
|
|
19253
19532
|
continue;
|
|
@@ -20771,12 +21050,13 @@ function extractGuardReferences(content) {
|
|
|
20771
21050
|
}
|
|
20772
21051
|
|
|
20773
21052
|
// packages/validator/src/walker.ts
|
|
20774
|
-
function walkText(ast, callback) {
|
|
21053
|
+
function walkText(ast, callback, options) {
|
|
21054
|
+
const exclude = options?.excludeProperties ? new Set(options.excludeProperties) : void 0;
|
|
20775
21055
|
for (const block of ast.blocks) {
|
|
20776
|
-
walkBlockContent(block.content, block.loc, callback);
|
|
21056
|
+
walkBlockContent(block.content, block.loc, callback, exclude);
|
|
20777
21057
|
}
|
|
20778
21058
|
for (const ext of ast.extends) {
|
|
20779
|
-
walkBlockContent(ext.content, ext.loc, callback);
|
|
21059
|
+
walkBlockContent(ext.content, ext.loc, callback, exclude);
|
|
20780
21060
|
}
|
|
20781
21061
|
}
|
|
20782
21062
|
function walkBlocks(ast, callback) {
|
|
@@ -20792,40 +21072,41 @@ function walkUses(ast, callback) {
|
|
|
20792
21072
|
callback(use);
|
|
20793
21073
|
}
|
|
20794
21074
|
}
|
|
20795
|
-
function walkBlockContent(content, fallbackLoc, callback) {
|
|
21075
|
+
function walkBlockContent(content, fallbackLoc, callback, exclude) {
|
|
20796
21076
|
switch (content.type) {
|
|
20797
21077
|
case "TextContent":
|
|
20798
21078
|
callback(content.value, content.loc ?? fallbackLoc);
|
|
20799
21079
|
break;
|
|
20800
21080
|
case "ObjectContent":
|
|
20801
|
-
walkObjectProperties(content.properties, content.loc ?? fallbackLoc, callback);
|
|
21081
|
+
walkObjectProperties(content.properties, content.loc ?? fallbackLoc, callback, exclude);
|
|
20802
21082
|
break;
|
|
20803
21083
|
case "ArrayContent":
|
|
20804
|
-
walkArrayElements(content.elements, content.loc ?? fallbackLoc, callback);
|
|
21084
|
+
walkArrayElements(content.elements, content.loc ?? fallbackLoc, callback, exclude);
|
|
20805
21085
|
break;
|
|
20806
21086
|
case "MixedContent":
|
|
20807
21087
|
if (content.text) {
|
|
20808
21088
|
callback(content.text.value, content.text.loc ?? fallbackLoc);
|
|
20809
21089
|
}
|
|
20810
|
-
walkObjectProperties(content.properties, content.loc ?? fallbackLoc, callback);
|
|
21090
|
+
walkObjectProperties(content.properties, content.loc ?? fallbackLoc, callback, exclude);
|
|
20811
21091
|
break;
|
|
20812
21092
|
}
|
|
20813
21093
|
}
|
|
20814
|
-
function walkObjectProperties(properties, loc, callback) {
|
|
20815
|
-
for (const value of Object.
|
|
20816
|
-
|
|
21094
|
+
function walkObjectProperties(properties, loc, callback, exclude) {
|
|
21095
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
21096
|
+
if (exclude?.has(key)) continue;
|
|
21097
|
+
walkValue(value, loc, callback, exclude);
|
|
20817
21098
|
}
|
|
20818
21099
|
}
|
|
20819
|
-
function walkArrayElements(elements, loc, callback) {
|
|
21100
|
+
function walkArrayElements(elements, loc, callback, exclude) {
|
|
20820
21101
|
for (const element of elements) {
|
|
20821
|
-
walkValue(element, loc, callback);
|
|
21102
|
+
walkValue(element, loc, callback, exclude);
|
|
20822
21103
|
}
|
|
20823
21104
|
}
|
|
20824
|
-
function walkValue(value, loc, callback) {
|
|
21105
|
+
function walkValue(value, loc, callback, exclude) {
|
|
20825
21106
|
if (typeof value === "string") {
|
|
20826
21107
|
callback(value, loc);
|
|
20827
21108
|
} else if (Array.isArray(value)) {
|
|
20828
|
-
walkArrayElements(value, loc, callback);
|
|
21109
|
+
walkArrayElements(value, loc, callback, exclude);
|
|
20829
21110
|
} else if (value !== null && typeof value === "object") {
|
|
20830
21111
|
if ("type" in value) {
|
|
20831
21112
|
const typed = value;
|
|
@@ -20833,9 +21114,27 @@ function walkValue(value, loc, callback) {
|
|
|
20833
21114
|
callback(typed.value, typed.loc ?? loc);
|
|
20834
21115
|
}
|
|
20835
21116
|
} else {
|
|
20836
|
-
walkObjectProperties(value, loc, callback);
|
|
21117
|
+
walkObjectProperties(value, loc, callback, exclude);
|
|
21118
|
+
}
|
|
21119
|
+
}
|
|
21120
|
+
}
|
|
21121
|
+
function offsetLocation(baseLoc, text, charIndex) {
|
|
21122
|
+
let line = baseLoc.line;
|
|
21123
|
+
let column = baseLoc.column;
|
|
21124
|
+
for (let i = 0; i < charIndex && i < text.length; i++) {
|
|
21125
|
+
if (text[i] === "\n") {
|
|
21126
|
+
line++;
|
|
21127
|
+
column = 1;
|
|
21128
|
+
} else {
|
|
21129
|
+
column++;
|
|
20837
21130
|
}
|
|
20838
21131
|
}
|
|
21132
|
+
return {
|
|
21133
|
+
file: baseLoc.file,
|
|
21134
|
+
line,
|
|
21135
|
+
column,
|
|
21136
|
+
offset: baseLoc.offset !== void 0 ? baseLoc.offset + charIndex : void 0
|
|
21137
|
+
};
|
|
20839
21138
|
}
|
|
20840
21139
|
function hasContent(content) {
|
|
20841
21140
|
switch (content.type) {
|
|
@@ -21225,6 +21524,12 @@ var BLOCKED_PATTERNS_ALL_LANGUAGES = [
|
|
|
21225
21524
|
...BLOCKED_PATTERNS_RO,
|
|
21226
21525
|
...BLOCKED_PATTERNS_HE
|
|
21227
21526
|
];
|
|
21527
|
+
var NEGATION_PREFIX = /(?:prevent|avoid|block|detect|flag|report|stop|prohibit|anti[- ]|against|don['']?t|do\s+not|never|must\s+not|should\s+not|cannot|warning.*about|protection\s+(?:from|against))\s+/i;
|
|
21528
|
+
function isNegatedMatch(text, matchIndex) {
|
|
21529
|
+
const lookbackStart = Math.max(0, matchIndex - 60);
|
|
21530
|
+
const prefix = text.slice(lookbackStart, matchIndex);
|
|
21531
|
+
return NEGATION_PREFIX.test(prefix);
|
|
21532
|
+
}
|
|
21228
21533
|
var blockedPatterns = {
|
|
21229
21534
|
id: "PS005",
|
|
21230
21535
|
name: "blocked-patterns",
|
|
@@ -21237,17 +21542,30 @@ var blockedPatterns = {
|
|
|
21237
21542
|
(p) => typeof p === "string" ? new RegExp(p, "i") : p
|
|
21238
21543
|
)
|
|
21239
21544
|
];
|
|
21240
|
-
walkText(
|
|
21241
|
-
|
|
21242
|
-
|
|
21243
|
-
|
|
21244
|
-
|
|
21245
|
-
|
|
21246
|
-
|
|
21247
|
-
|
|
21545
|
+
walkText(
|
|
21546
|
+
ctx.ast,
|
|
21547
|
+
(text, loc) => {
|
|
21548
|
+
for (const pattern of patterns) {
|
|
21549
|
+
const globalPattern = new RegExp(
|
|
21550
|
+
pattern.source,
|
|
21551
|
+
pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g"
|
|
21552
|
+
);
|
|
21553
|
+
let match;
|
|
21554
|
+
while ((match = globalPattern.exec(text)) !== null) {
|
|
21555
|
+
if (isNegatedMatch(text, match.index)) {
|
|
21556
|
+
continue;
|
|
21557
|
+
}
|
|
21558
|
+
ctx.report({
|
|
21559
|
+
message: `Blocked pattern detected: ${pattern.source}`,
|
|
21560
|
+
location: offsetLocation(loc, text, match.index),
|
|
21561
|
+
suggestion: "Remove or rephrase the flagged content"
|
|
21562
|
+
});
|
|
21563
|
+
break;
|
|
21564
|
+
}
|
|
21248
21565
|
}
|
|
21249
|
-
}
|
|
21250
|
-
|
|
21566
|
+
},
|
|
21567
|
+
{ excludeProperties: ["resources"] }
|
|
21568
|
+
);
|
|
21251
21569
|
}
|
|
21252
21570
|
};
|
|
21253
21571
|
|
|
@@ -21640,87 +21958,91 @@ var suspiciousUrls = {
|
|
|
21640
21958
|
description: "Detect suspicious URLs (HTTP, shorteners, credential parameters, IDN homograph attacks)",
|
|
21641
21959
|
defaultSeverity: "warning",
|
|
21642
21960
|
validate: (ctx) => {
|
|
21643
|
-
walkText(
|
|
21644
|
-
|
|
21645
|
-
|
|
21646
|
-
|
|
21647
|
-
|
|
21648
|
-
|
|
21649
|
-
location: loc,
|
|
21650
|
-
suggestion: "Use HTTPS instead of HTTP for secure communication"
|
|
21651
|
-
});
|
|
21652
|
-
}
|
|
21653
|
-
}
|
|
21654
|
-
for (const pattern of URL_SHORTENER_PATTERNS) {
|
|
21655
|
-
pattern.lastIndex = 0;
|
|
21656
|
-
const shortenerMatches = text.match(pattern);
|
|
21657
|
-
if (shortenerMatches) {
|
|
21658
|
-
for (const match of shortenerMatches) {
|
|
21961
|
+
walkText(
|
|
21962
|
+
ctx.ast,
|
|
21963
|
+
(text, loc) => {
|
|
21964
|
+
const httpMatches = text.match(HTTP_URL_PATTERN);
|
|
21965
|
+
if (httpMatches) {
|
|
21966
|
+
for (const match of httpMatches) {
|
|
21659
21967
|
ctx.report({
|
|
21660
|
-
message: `URL
|
|
21968
|
+
message: `Insecure HTTP URL detected: ${match}`,
|
|
21661
21969
|
location: loc,
|
|
21662
|
-
suggestion: "Use
|
|
21970
|
+
suggestion: "Use HTTPS instead of HTTP for secure communication"
|
|
21663
21971
|
});
|
|
21664
21972
|
}
|
|
21665
21973
|
}
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
|
|
21670
|
-
|
|
21671
|
-
message: `URL with suspicious credential parameter detected: ${match}`,
|
|
21672
|
-
location: loc,
|
|
21673
|
-
suggestion: "Avoid embedding credentials or tokens in URLs"
|
|
21674
|
-
});
|
|
21675
|
-
}
|
|
21676
|
-
}
|
|
21677
|
-
PUNYCODE_URL_PATTERN.lastIndex = 0;
|
|
21678
|
-
const punycodeMatches = text.match(PUNYCODE_URL_PATTERN);
|
|
21679
|
-
if (punycodeMatches) {
|
|
21680
|
-
for (const match of punycodeMatches) {
|
|
21681
|
-
const domain = extractDomain(match);
|
|
21682
|
-
if (domain) {
|
|
21683
|
-
const impersonatedService = detectImpersonatedService(domain);
|
|
21684
|
-
if (impersonatedService) {
|
|
21974
|
+
for (const pattern of URL_SHORTENER_PATTERNS) {
|
|
21975
|
+
pattern.lastIndex = 0;
|
|
21976
|
+
const shortenerMatches = text.match(pattern);
|
|
21977
|
+
if (shortenerMatches) {
|
|
21978
|
+
for (const match of shortenerMatches) {
|
|
21685
21979
|
ctx.report({
|
|
21686
|
-
message: `
|
|
21980
|
+
message: `URL shortener detected: ${match}`,
|
|
21687
21981
|
location: loc,
|
|
21688
|
-
suggestion: "
|
|
21689
|
-
});
|
|
21690
|
-
} else {
|
|
21691
|
-
ctx.report({
|
|
21692
|
-
message: `Punycode domain detected: ${match}`,
|
|
21693
|
-
location: loc,
|
|
21694
|
-
suggestion: "Punycode domains (xn--) can be legitimate international domains, but may also be used for homograph attacks. Verify the domain is intentional."
|
|
21982
|
+
suggestion: "Use full URLs instead of shorteners to ensure destination transparency"
|
|
21695
21983
|
});
|
|
21696
21984
|
}
|
|
21697
21985
|
}
|
|
21698
21986
|
}
|
|
21699
|
-
|
|
21700
|
-
|
|
21701
|
-
|
|
21702
|
-
|
|
21703
|
-
|
|
21704
|
-
|
|
21705
|
-
|
|
21706
|
-
|
|
21707
|
-
|
|
21708
|
-
|
|
21709
|
-
|
|
21710
|
-
|
|
21711
|
-
|
|
21712
|
-
|
|
21713
|
-
|
|
21714
|
-
|
|
21715
|
-
|
|
21716
|
-
|
|
21717
|
-
|
|
21718
|
-
|
|
21987
|
+
const paramMatches = text.match(SUSPICIOUS_PARAM_PATTERN);
|
|
21988
|
+
if (paramMatches) {
|
|
21989
|
+
for (const match of paramMatches) {
|
|
21990
|
+
ctx.report({
|
|
21991
|
+
message: `URL with suspicious credential parameter detected: ${match}`,
|
|
21992
|
+
location: loc,
|
|
21993
|
+
suggestion: "Avoid embedding credentials or tokens in URLs"
|
|
21994
|
+
});
|
|
21995
|
+
}
|
|
21996
|
+
}
|
|
21997
|
+
PUNYCODE_URL_PATTERN.lastIndex = 0;
|
|
21998
|
+
const punycodeMatches = text.match(PUNYCODE_URL_PATTERN);
|
|
21999
|
+
if (punycodeMatches) {
|
|
22000
|
+
for (const match of punycodeMatches) {
|
|
22001
|
+
const domain = extractDomain(match);
|
|
22002
|
+
if (domain) {
|
|
22003
|
+
const impersonatedService = detectImpersonatedService(domain);
|
|
22004
|
+
if (impersonatedService) {
|
|
22005
|
+
ctx.report({
|
|
22006
|
+
message: `Potential IDN homograph attack detected: ${match} may be impersonating "${impersonatedService}"`,
|
|
22007
|
+
location: loc,
|
|
22008
|
+
suggestion: "Punycode domains (xn--) can visually impersonate legitimate sites. Verify the actual domain carefully."
|
|
22009
|
+
});
|
|
22010
|
+
} else {
|
|
22011
|
+
ctx.report({
|
|
22012
|
+
message: `Punycode domain detected: ${match}`,
|
|
22013
|
+
location: loc,
|
|
22014
|
+
suggestion: "Punycode domains (xn--) can be legitimate international domains, but may also be used for homograph attacks. Verify the domain is intentional."
|
|
22015
|
+
});
|
|
22016
|
+
}
|
|
21719
22017
|
}
|
|
21720
22018
|
}
|
|
21721
22019
|
}
|
|
21722
|
-
|
|
21723
|
-
|
|
22020
|
+
ALL_URLS_PATTERN.lastIndex = 0;
|
|
22021
|
+
const allUrls = text.match(ALL_URLS_PATTERN);
|
|
22022
|
+
if (allUrls) {
|
|
22023
|
+
for (const url of allUrls) {
|
|
22024
|
+
const domain = extractDomain(url);
|
|
22025
|
+
if (domain) {
|
|
22026
|
+
const { isAttack, impersonatedService, mixedScripts } = checkHomographAttack(domain);
|
|
22027
|
+
if (isAttack && impersonatedService) {
|
|
22028
|
+
ctx.report({
|
|
22029
|
+
message: `IDN homograph attack detected: "${domain}" uses deceptive characters to impersonate "${impersonatedService}"`,
|
|
22030
|
+
location: loc,
|
|
22031
|
+
suggestion: `This domain uses ${mixedScripts ? `mixed scripts (${mixedScripts.join("+")})` : "homoglyph characters"} to visually mimic a legitimate service. Do not trust this URL.`
|
|
22032
|
+
});
|
|
22033
|
+
} else if (mixedScripts && mixedScripts.length > 1) {
|
|
22034
|
+
ctx.report({
|
|
22035
|
+
message: `Mixed script domain detected: "${domain}" uses ${mixedScripts.join(" + ")} characters`,
|
|
22036
|
+
location: loc,
|
|
22037
|
+
suggestion: "Domains mixing different character scripts (e.g., Latin + Cyrillic) may be attempts to deceive users. Verify this is intentional."
|
|
22038
|
+
});
|
|
22039
|
+
}
|
|
22040
|
+
}
|
|
22041
|
+
}
|
|
22042
|
+
}
|
|
22043
|
+
},
|
|
22044
|
+
{ excludeProperties: ["resources"] }
|
|
22045
|
+
);
|
|
21724
22046
|
}
|
|
21725
22047
|
};
|
|
21726
22048
|
|
|
@@ -21760,17 +22082,21 @@ var authorityInjection = {
|
|
|
21760
22082
|
description: "Detect authoritative override phrases that may indicate prompt injection",
|
|
21761
22083
|
defaultSeverity: "error",
|
|
21762
22084
|
validate: (ctx) => {
|
|
21763
|
-
walkText(
|
|
21764
|
-
|
|
21765
|
-
|
|
21766
|
-
|
|
21767
|
-
|
|
21768
|
-
|
|
21769
|
-
|
|
21770
|
-
|
|
22085
|
+
walkText(
|
|
22086
|
+
ctx.ast,
|
|
22087
|
+
(text, loc) => {
|
|
22088
|
+
for (const pattern of AUTHORITY_PATTERNS) {
|
|
22089
|
+
if (pattern.test(text)) {
|
|
22090
|
+
ctx.report({
|
|
22091
|
+
message: `Authority injection pattern detected: ${pattern.source}`,
|
|
22092
|
+
location: loc,
|
|
22093
|
+
suggestion: "Remove authoritative override language that could be used for prompt injection"
|
|
22094
|
+
});
|
|
22095
|
+
}
|
|
21771
22096
|
}
|
|
21772
|
-
}
|
|
21773
|
-
|
|
22097
|
+
},
|
|
22098
|
+
{ excludeProperties: ["resources"] }
|
|
22099
|
+
);
|
|
21774
22100
|
}
|
|
21775
22101
|
};
|
|
21776
22102
|
|
|
@@ -22156,26 +22482,30 @@ var obfuscatedContent = {
|
|
|
22156
22482
|
description: "Detect obfuscated content using sanitization pipeline (decode and check all encodings)",
|
|
22157
22483
|
defaultSeverity: "warning",
|
|
22158
22484
|
validate: (ctx) => {
|
|
22159
|
-
walkText(
|
|
22160
|
-
|
|
22161
|
-
|
|
22162
|
-
|
|
22163
|
-
|
|
22164
|
-
|
|
22165
|
-
|
|
22166
|
-
|
|
22167
|
-
|
|
22168
|
-
|
|
22169
|
-
|
|
22170
|
-
|
|
22171
|
-
|
|
22172
|
-
|
|
22173
|
-
|
|
22174
|
-
|
|
22175
|
-
|
|
22176
|
-
|
|
22177
|
-
|
|
22178
|
-
|
|
22485
|
+
walkText(
|
|
22486
|
+
ctx.ast,
|
|
22487
|
+
(text, loc) => {
|
|
22488
|
+
const encodedMatches = detectAndDecodeEncodings(text);
|
|
22489
|
+
for (const match of encodedMatches) {
|
|
22490
|
+
ctx.report({
|
|
22491
|
+
message: `Malicious content detected in ${match.type}: ${match.issues.join(", ")}. Decoded: "${match.decoded.substring(0, 50)}${match.decoded.length > 50 ? "..." : ""}"`,
|
|
22492
|
+
location: loc,
|
|
22493
|
+
suggestion: "Encoded content is automatically decoded and checked for security patterns. Remove the malicious payload or use plain text."
|
|
22494
|
+
});
|
|
22495
|
+
}
|
|
22496
|
+
const hasLongBase64 = (text.match(BASE64_PATTERN) || []).some(
|
|
22497
|
+
(m) => m.length >= MIN_ENCODED_LENGTH * 2 && !isLikelyLegitimateBase64(text, m)
|
|
22498
|
+
);
|
|
22499
|
+
if (hasLongBase64 && encodedMatches.length === 0) {
|
|
22500
|
+
ctx.report({
|
|
22501
|
+
message: "Long Base64-encoded content detected that may hide payloads",
|
|
22502
|
+
location: loc,
|
|
22503
|
+
suggestion: "If this is intentional, consider using plain text or documenting the purpose"
|
|
22504
|
+
});
|
|
22505
|
+
}
|
|
22506
|
+
},
|
|
22507
|
+
{ excludeProperties: ["resources"] }
|
|
22508
|
+
);
|
|
22179
22509
|
}
|
|
22180
22510
|
};
|
|
22181
22511
|
|
|
@@ -22292,22 +22622,32 @@ var GREEK_LOOKALIKES = /* @__PURE__ */ new Map([
|
|
|
22292
22622
|
["\u0396", "\u0396 (Greek) looks like Z (Latin)"]
|
|
22293
22623
|
]);
|
|
22294
22624
|
var EXCESSIVE_COMBINING_PATTERN = /[\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u064B-\u065F]{4,}/;
|
|
22295
|
-
var
|
|
22625
|
+
var LATIN_CHAR = /[a-zA-Z]/;
|
|
22626
|
+
var CYRILLIC_CHAR = /[\u0400-\u04FF]/;
|
|
22627
|
+
var GREEK_CHAR = /[\u0370-\u03FF]/;
|
|
22628
|
+
var WORD_PATTERN = /[a-zA-Z\u0370-\u03FF\u0400-\u04FF]+/g;
|
|
22296
22629
|
function findHomographAttacks(text) {
|
|
22297
22630
|
const results = [];
|
|
22298
|
-
|
|
22299
|
-
|
|
22300
|
-
|
|
22301
|
-
|
|
22302
|
-
|
|
22303
|
-
const
|
|
22304
|
-
const
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
22631
|
+
let wordMatch;
|
|
22632
|
+
const wordPattern = new RegExp(WORD_PATTERN.source, WORD_PATTERN.flags);
|
|
22633
|
+
while ((wordMatch = wordPattern.exec(text)) !== null) {
|
|
22634
|
+
const word = wordMatch[0];
|
|
22635
|
+
const wordStart = wordMatch.index;
|
|
22636
|
+
const hasLatin = LATIN_CHAR.test(word);
|
|
22637
|
+
const hasCyrillic = CYRILLIC_CHAR.test(word);
|
|
22638
|
+
const hasGreek = GREEK_CHAR.test(word);
|
|
22639
|
+
if (!hasLatin) continue;
|
|
22640
|
+
if (!hasCyrillic && !hasGreek) continue;
|
|
22641
|
+
for (let i = 0; i < word.length; i++) {
|
|
22642
|
+
const char = word[i];
|
|
22643
|
+
const cyrillicDesc = CYRILLIC_LOOKALIKES.get(char);
|
|
22644
|
+
if (cyrillicDesc) {
|
|
22645
|
+
results.push({ char, description: cyrillicDesc, index: wordStart + i });
|
|
22646
|
+
}
|
|
22647
|
+
const greekDesc = GREEK_LOOKALIKES.get(char);
|
|
22648
|
+
if (greekDesc) {
|
|
22649
|
+
results.push({ char, description: greekDesc, index: wordStart + i });
|
|
22650
|
+
}
|
|
22311
22651
|
}
|
|
22312
22652
|
}
|
|
22313
22653
|
return results;
|
|
@@ -22340,47 +22680,51 @@ var unicodeSecurity = {
|
|
|
22340
22680
|
description: "Detect Unicode-based attacks (bidi overrides, zero-width, homoglyphs)",
|
|
22341
22681
|
defaultSeverity: "error",
|
|
22342
22682
|
validate: (ctx) => {
|
|
22343
|
-
walkText(
|
|
22344
|
-
|
|
22345
|
-
|
|
22346
|
-
const
|
|
22347
|
-
|
|
22348
|
-
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
|
|
22352
|
-
|
|
22353
|
-
|
|
22354
|
-
|
|
22355
|
-
|
|
22356
|
-
const
|
|
22357
|
-
|
|
22358
|
-
|
|
22359
|
-
|
|
22360
|
-
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
|
|
22365
|
-
|
|
22366
|
-
const
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
22370
|
-
|
|
22371
|
-
|
|
22372
|
-
|
|
22373
|
-
|
|
22374
|
-
|
|
22375
|
-
|
|
22376
|
-
const
|
|
22377
|
-
|
|
22378
|
-
|
|
22379
|
-
|
|
22380
|
-
|
|
22381
|
-
|
|
22382
|
-
|
|
22383
|
-
|
|
22683
|
+
walkText(
|
|
22684
|
+
ctx.ast,
|
|
22685
|
+
(text, loc) => {
|
|
22686
|
+
const bidiChars = findBidiChars(text);
|
|
22687
|
+
if (bidiChars.length > 0) {
|
|
22688
|
+
const descriptions = bidiChars.slice(0, 3).map((b) => b.description).join(", ");
|
|
22689
|
+
const suffix = bidiChars.length > 3 ? ` and ${bidiChars.length - 3} more` : "";
|
|
22690
|
+
ctx.report({
|
|
22691
|
+
message: `Bidirectional text override detected: ${descriptions}${suffix}`,
|
|
22692
|
+
location: offsetLocation(loc, text, bidiChars[0].index),
|
|
22693
|
+
suggestion: "Remove bidirectional override characters that may hide malicious content"
|
|
22694
|
+
});
|
|
22695
|
+
}
|
|
22696
|
+
const zeroWidthChars = findZeroWidthChars(text);
|
|
22697
|
+
if (zeroWidthChars.length > 0) {
|
|
22698
|
+
const descriptions = zeroWidthChars.slice(0, 3).map((z) => z.description).join(", ");
|
|
22699
|
+
const suffix = zeroWidthChars.length > 3 ? ` and ${zeroWidthChars.length - 3} more` : "";
|
|
22700
|
+
ctx.report({
|
|
22701
|
+
message: `Zero-width characters detected: ${descriptions}${suffix}`,
|
|
22702
|
+
location: offsetLocation(loc, text, zeroWidthChars[0].index),
|
|
22703
|
+
suggestion: "Remove zero-width characters that may be used to evade pattern matching"
|
|
22704
|
+
});
|
|
22705
|
+
}
|
|
22706
|
+
const homographs = findHomographAttacks(text);
|
|
22707
|
+
if (homographs.length > 0) {
|
|
22708
|
+
const descriptions = homographs.slice(0, 3).map((h) => h.description).join(", ");
|
|
22709
|
+
const suffix = homographs.length > 3 ? ` and ${homographs.length - 3} more` : "";
|
|
22710
|
+
ctx.report({
|
|
22711
|
+
message: `Potential homograph attack: ${descriptions}${suffix}`,
|
|
22712
|
+
location: offsetLocation(loc, text, homographs[0].index),
|
|
22713
|
+
suggestion: "Replace lookalike Cyrillic/Greek characters with Latin equivalents"
|
|
22714
|
+
});
|
|
22715
|
+
}
|
|
22716
|
+
const combiningMatch = EXCESSIVE_COMBINING_PATTERN.exec(text);
|
|
22717
|
+
if (combiningMatch) {
|
|
22718
|
+
const charCodes = Array.from(combiningMatch[0]).slice(0, 4).map((c) => `U+${c.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")}`).join(", ");
|
|
22719
|
+
ctx.report({
|
|
22720
|
+
message: `Excessive combining characters (Zalgo text) detected: ${charCodes}...`,
|
|
22721
|
+
location: offsetLocation(loc, text, combiningMatch.index),
|
|
22722
|
+
suggestion: "Remove excessive diacritical marks that may be used to obscure content"
|
|
22723
|
+
});
|
|
22724
|
+
}
|
|
22725
|
+
},
|
|
22726
|
+
{ excludeProperties: ["resources"] }
|
|
22727
|
+
);
|
|
22384
22728
|
}
|
|
22385
22729
|
};
|
|
22386
22730
|
|
|
@@ -23181,8 +23525,14 @@ async function writeOutputs(outputs, options, _config, services) {
|
|
|
23181
23525
|
result.written.push(outputPath);
|
|
23182
23526
|
}
|
|
23183
23527
|
} else {
|
|
23184
|
-
|
|
23185
|
-
|
|
23528
|
+
const existingContent = await readFile7(outputPath, "utf-8");
|
|
23529
|
+
if (existingContent === output.content) {
|
|
23530
|
+
ConsoleOutput.dryRun(`Unchanged: ${outputPath}`);
|
|
23531
|
+
result.unchanged.push(outputPath);
|
|
23532
|
+
} else {
|
|
23533
|
+
ConsoleOutput.warning(`Would conflict: ${outputPath} (not generated by PromptScript)`);
|
|
23534
|
+
result.written.push(outputPath);
|
|
23535
|
+
}
|
|
23186
23536
|
}
|
|
23187
23537
|
} else {
|
|
23188
23538
|
ConsoleOutput.dryRun(`Would write: ${outputPath}`);
|
|
@@ -23213,6 +23563,17 @@ async function writeOutputs(outputs, options, _config, services) {
|
|
|
23213
23563
|
result.written.push(outputPath);
|
|
23214
23564
|
continue;
|
|
23215
23565
|
}
|
|
23566
|
+
let contentMatches = false;
|
|
23567
|
+
try {
|
|
23568
|
+
const existingContent = await readFile7(outputPath, "utf-8");
|
|
23569
|
+
contentMatches = existingContent === output.content;
|
|
23570
|
+
} catch {
|
|
23571
|
+
}
|
|
23572
|
+
if (contentMatches) {
|
|
23573
|
+
ConsoleOutput.unchanged(outputPath);
|
|
23574
|
+
result.unchanged.push(outputPath);
|
|
23575
|
+
continue;
|
|
23576
|
+
}
|
|
23216
23577
|
if (options.force || overwriteAll) {
|
|
23217
23578
|
await mkdir2(dirname6(outputPath), { recursive: true });
|
|
23218
23579
|
await writeFile2(outputPath, output.content, "utf-8");
|
package/package.json
CHANGED
|
@@ -215,7 +215,8 @@ userInvocable, allowedTools, context ("fork" or "inherit"), agent.
|
|
|
215
215
|
|
|
216
216
|
### @agents
|
|
217
217
|
|
|
218
|
-
Custom subagent definitions
|
|
218
|
+
Custom subagent definitions. Compiles to `.claude/agents/` for Claude Code,
|
|
219
|
+
`.github/agents/` for GitHub Copilot, `.factory/droids/` for Factory AI, etc.
|
|
219
220
|
|
|
220
221
|
```
|
|
221
222
|
@agents {
|
|
@@ -229,6 +230,14 @@ Custom subagent definitions:
|
|
|
229
230
|
}
|
|
230
231
|
```
|
|
231
232
|
|
|
233
|
+
Supports mixed models per agent: `specModel` sets a different model for
|
|
234
|
+
Specification/planning mode (GitHub, Factory), `specReasoningEffort` sets reasoning
|
|
235
|
+
effort for the spec model (Factory only, values: "low", "medium", "high").
|
|
236
|
+
|
|
237
|
+
Factory AI droids support additional properties: `model` (any model ID or "inherit"),
|
|
238
|
+
`reasoningEffort` ("low", "medium", "high"), and `tools` (category name like "read-only"
|
|
239
|
+
or array of tool IDs).
|
|
240
|
+
|
|
232
241
|
### @knowledge
|
|
233
242
|
|
|
234
243
|
Reference documentation as triple-quoted text. Used for command references,
|
|
@@ -331,21 +340,21 @@ prs diff --target claude # Show compilation diff
|
|
|
331
340
|
|
|
332
341
|
38 supported targets. Key examples:
|
|
333
342
|
|
|
334
|
-
| Target | Main File | Skills
|
|
335
|
-
| ----------- | ------------------------------- |
|
|
336
|
-
| GitHub | .github/copilot-instructions.md | .github/skills/\*/SKILL.md
|
|
337
|
-
| Claude | CLAUDE.md | .claude/skills/\*/SKILL.md
|
|
338
|
-
| Cursor | .cursor/rules/project.mdc | .cursor/commands/\*.md
|
|
339
|
-
| Antigravity | .agent/rules/project.md | .agent/rules/\*.md
|
|
340
|
-
| Factory | AGENTS.md | .factory/skills/\*/SKILL.md
|
|
341
|
-
| OpenCode | OPENCODE.md | .opencode/skills/\*/SKILL.md
|
|
342
|
-
| Gemini | GEMINI.md | .gemini/skills/\*/skill.md
|
|
343
|
-
| Windsurf | .windsurf/rules/project.md | .windsurf/skills/\*/SKILL.md
|
|
344
|
-
| Cline | .clinerules | .agents/skills/\*/SKILL.md
|
|
345
|
-
| Roo Code | .roorules | .roo/skills/\*/SKILL.md
|
|
346
|
-
| Codex | AGENTS.md | .agents/skills/\*/SKILL.md
|
|
347
|
-
| Continue | .continue/rules/project.md | .continue/skills/\*/SKILL.md
|
|
348
|
-
| + 26 more | | See full list in documentation
|
|
343
|
+
| Target | Main File | Skills |
|
|
344
|
+
| ----------- | ------------------------------- | -------------------------------------------------- |
|
|
345
|
+
| GitHub | .github/copilot-instructions.md | .github/skills/\*/SKILL.md |
|
|
346
|
+
| Claude | CLAUDE.md | .claude/skills/\*/SKILL.md |
|
|
347
|
+
| Cursor | .cursor/rules/project.mdc | .cursor/commands/\*.md |
|
|
348
|
+
| Antigravity | .agent/rules/project.md | .agent/rules/\*.md |
|
|
349
|
+
| Factory | AGENTS.md | .factory/skills/\*/SKILL.md, .factory/droids/\*.md |
|
|
350
|
+
| OpenCode | OPENCODE.md | .opencode/skills/\*/SKILL.md |
|
|
351
|
+
| Gemini | GEMINI.md | .gemini/skills/\*/skill.md |
|
|
352
|
+
| Windsurf | .windsurf/rules/project.md | .windsurf/skills/\*/SKILL.md |
|
|
353
|
+
| Cline | .clinerules | .agents/skills/\*/SKILL.md |
|
|
354
|
+
| Roo Code | .roorules | .roo/skills/\*/SKILL.md |
|
|
355
|
+
| Codex | AGENTS.md | .agents/skills/\*/SKILL.md |
|
|
356
|
+
| Continue | .continue/rules/project.md | .continue/skills/\*/SKILL.md |
|
|
357
|
+
| + 26 more | | See full list in documentation |
|
|
349
358
|
|
|
350
359
|
## Project Organization
|
|
351
360
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/commands/compile.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQlD,OAAO,EAAE,KAAK,WAAW,EAAyB,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../../../../packages/cli/src/commands/compile.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQlD,OAAO,EAAE,KAAK,WAAW,EAAyB,MAAM,gBAAgB,CAAC;AAgVzE;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,QAAQ,GAAE,WAAqC,GAC9C,OAAO,CAAC,IAAI,CAAC,CA8Gf"}
|