@agentlee5/agent-skills 1.0.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/.leeway/config.json +133 -0
- package/LICENSE +21 -0
- package/LeeWay-Standards/LICENSE +21 -0
- package/LeeWay-Standards/README.md +324 -0
- package/LeeWay-Standards/examples/NexusButton.tsx +90 -0
- package/LeeWay-Standards/examples/example-agent.js +89 -0
- package/LeeWay-Standards/package.json +61 -0
- package/LeeWay-Standards/schemas/leeway-config.schema.json +81 -0
- package/LeeWay-Standards/schemas/leeway-header.schema.json +63 -0
- package/LeeWay-Standards/src/agents/discovery/architecture-map-agent.js +134 -0
- package/LeeWay-Standards/src/agents/discovery/docs-agent.js +126 -0
- package/LeeWay-Standards/src/agents/discovery/explain-agent.js +95 -0
- package/LeeWay-Standards/src/agents/discovery/intent-registry-agent.js +119 -0
- package/LeeWay-Standards/src/agents/discovery/schema-agent.js +116 -0
- package/LeeWay-Standards/src/agents/discovery/sitemap-agent.js +88 -0
- package/LeeWay-Standards/src/agents/governance/align-agent.js +155 -0
- package/LeeWay-Standards/src/agents/governance/assess-agent.js +161 -0
- package/LeeWay-Standards/src/agents/governance/audit-agent.js +185 -0
- package/LeeWay-Standards/src/agents/integrity/circular-dependency-agent.js +88 -0
- package/LeeWay-Standards/src/agents/integrity/dependency-graph-agent.js +107 -0
- package/LeeWay-Standards/src/agents/integrity/duplicate-logic-agent.js +108 -0
- package/LeeWay-Standards/src/agents/integrity/import-agent.js +83 -0
- package/LeeWay-Standards/src/agents/integrity/module-policy-agent.js +94 -0
- package/LeeWay-Standards/src/agents/integrity/refactor-scan-agent.js +113 -0
- package/LeeWay-Standards/src/agents/integrity/syntax-agent.js +84 -0
- package/LeeWay-Standards/src/agents/mcp/endpoint-agent.js +106 -0
- package/LeeWay-Standards/src/agents/mcp/env-agent.js +111 -0
- package/LeeWay-Standards/src/agents/mcp/health-agent-lite.js +119 -0
- package/LeeWay-Standards/src/agents/mcp/manifest-agent.js +87 -0
- package/LeeWay-Standards/src/agents/mcp/port-agent.js +125 -0
- package/LeeWay-Standards/src/agents/mcp/process-agent.js +124 -0
- package/LeeWay-Standards/src/agents/mcp/runtime-agent.js +108 -0
- package/LeeWay-Standards/src/agents/mcp/transport-agent.js +78 -0
- package/LeeWay-Standards/src/agents/orchestration/doctor-agent.js +149 -0
- package/LeeWay-Standards/src/agents/orchestration/memory-agent-lite.js +125 -0
- package/LeeWay-Standards/src/agents/orchestration/router-agent.js +110 -0
- package/LeeWay-Standards/src/agents/security/permission-agent.js +98 -0
- package/LeeWay-Standards/src/agents/security/policy-agent.js +100 -0
- package/LeeWay-Standards/src/agents/security/privacy-agent.js +83 -0
- package/LeeWay-Standards/src/agents/security/prompt-security-agent.js +103 -0
- package/LeeWay-Standards/src/agents/security/secret-scan-agent.js +108 -0
- package/LeeWay-Standards/src/agents/security/tool-access-agent.js +105 -0
- package/LeeWay-Standards/src/agents/standards/authority-agent.js +114 -0
- package/LeeWay-Standards/src/agents/standards/discovery-pipeline-agent.js +91 -0
- package/LeeWay-Standards/src/agents/standards/header-agent.js +120 -0
- package/LeeWay-Standards/src/agents/standards/placement-agent.js +96 -0
- package/LeeWay-Standards/src/agents/standards/region-agent.js +99 -0
- package/LeeWay-Standards/src/agents/standards/registry-agent.js +153 -0
- package/LeeWay-Standards/src/agents/standards/tag-agent.js +111 -0
- package/LeeWay-Standards/src/cli/leeway.js +225 -0
- package/LeeWay-Standards/src/core/compliance-scorer.js +168 -0
- package/LeeWay-Standards/src/core/compliance-scorer.test.js +121 -0
- package/LeeWay-Standards/src/core/header-parser.js +207 -0
- package/LeeWay-Standards/src/core/header-parser.test.js +198 -0
- package/LeeWay-Standards/src/core/region-classifier.js +137 -0
- package/LeeWay-Standards/src/core/region-classifier.test.js +100 -0
- package/LeeWay-Standards/src/core/tag-validator.js +139 -0
- package/LeeWay-Standards/src/core/tag-validator.test.js +109 -0
- package/LeeWay-Standards/src/index.js +83 -0
- package/README.md +217 -0
- package/agent-config.yaml +456 -0
- package/agentbage.png.png +0 -0
- package/bin/leeway-skills-badge.js +52 -0
- package/bin/leeway-skills-mcp.js +48 -0
- package/bin/leeway-skills.js +160 -0
- package/bin/leeway-standards.js +49 -0
- package/config/.skillsignore +63 -0
- package/config/skills-config.json +70 -0
- package/documents/AGENT_LEARNING_REFERENCE.md +329 -0
- package/documents/AGENT_LEE_INTEGRATION.md +534 -0
- package/documents/COMPLETE_SYSTEM_OVERVIEW.md +502 -0
- package/documents/COMPREHENSIVE_SKILL_INTEGRATION_PLAN.md +644 -0
- package/documents/DIRECTORY_MAP.md +323 -0
- package/documents/EXTENDING.md +514 -0
- package/documents/FILE_DIRECTORY_GUIDE.md +427 -0
- package/documents/LEEWAY_BADGE_INTEGRATION.md +76 -0
- package/documents/LEEWAY_IMPLEMENTATION_SUMMARY.md +384 -0
- package/documents/LEEWAY_INTEGRATION_GUIDE.md +414 -0
- package/documents/LEEWAY_NPM_SDK.md +66 -0
- package/documents/LEEWAY_QUICK_START.md +288 -0
- package/documents/LEEWAY_SKILLS_BRANDING.md +375 -0
- package/documents/LEEWAY_SKILLS_MCP_SUMMARY.md +593 -0
- package/documents/LEEWAY_STANDARDS_COMPLIANCE.md +361 -0
- package/documents/LEEWAY_UNIFIED_ARCHITECTURE.md +473 -0
- package/documents/LEEWAY_WORKFLOWS_QUICK_REFERENCE.md +307 -0
- package/documents/LEEWAY_WORKFLOWS_STRATEGIC_PLAN.md +515 -0
- package/documents/LIFELONG_LEARNING_LAYER.md +478 -0
- package/documents/MCP_ARCHITECTURE.md +683 -0
- package/documents/QUICK_REFERENCE.md +301 -0
- package/documents/SETUP.md +325 -0
- package/documents/SETUP_SUMMARY.md +413 -0
- package/documents/SKILL_ACQUISITION_EXECUTIVE_SUMMARY.md +373 -0
- package/documents/SKILL_ACQUISITION_IMPLEMENTATION.md +692 -0
- package/documents/SKILL_ACQUISITION_MANIFEST.md +404 -0
- package/documents/SKILL_ACQUISITION_QUICK_REFERENCE.md +349 -0
- package/documents/SKILL_WORKFLOW_COMPOSITION_MATRIX.md +537 -0
- package/documents/STRUCTURE.md +382 -0
- package/documents/SYSTEM_TRANSFORMATION_SUMMARY.md +560 -0
- package/documents/USAGE.md +390 -0
- package/documents/WORKFLOW_ACQUISITION_MANIFEST.md +576 -0
- package/documents/aiskills.txt +460 -0
- package/mcp-server/README.md +697 -0
- package/mcp-server/dist/badge-proof.d.ts +66 -0
- package/mcp-server/dist/badge-proof.d.ts.map +1 -0
- package/mcp-server/dist/badge-proof.js +324 -0
- package/mcp-server/dist/badge-proof.js.map +1 -0
- package/mcp-server/dist/index.d.ts +64 -0
- package/mcp-server/dist/index.d.ts.map +1 -0
- package/mcp-server/dist/index.js +263 -0
- package/mcp-server/dist/index.js.map +1 -0
- package/mcp-server/dist/install-badge-proof.d.ts +3 -0
- package/mcp-server/dist/install-badge-proof.d.ts.map +1 -0
- package/mcp-server/dist/install-badge-proof.js +109 -0
- package/mcp-server/dist/install-badge-proof.js.map +1 -0
- package/mcp-server/package.json +43 -0
- package/mcp-server/src/badge-proof.ts +469 -0
- package/mcp-server/src/index.ts +355 -0
- package/mcp-server/src/install-badge-proof.ts +132 -0
- package/mcp-server/tsconfig.json +22 -0
- package/package.json +84 -0
- package/scripts/init-leeway.js +217 -0
- package/scripts/leeway-agents/compliance-monitor.js +374 -0
- package/scripts/leeway-agents/header-injector.js +321 -0
- package/scripts/skill-integration-toolkit.py +319 -0
- package/scripts/skills-registry.json +1117 -0
- package/scripts/sync-skills.ps1 +275 -0
- package/scripts/verify-leeway-setup.js +249 -0
- package/scripts/workflow-integration-toolkit.py +522 -0
- package/sdk/application-installer.js +92 -0
- package/sdk/index.js +43 -0
- package/sdk/paths.js +167 -0
- package/skills/agent-autonomy/autonomous-conductor/SKILL.md +206 -0
- package/skills/agent-autonomy/full-stack-delivery/SKILL.md +206 -0
- package/skills/agent-orchestration/multi-agent-orchestration/SKILL.md +68 -0
- package/skills/agent-patterns/agent-design-patterns/SKILL.md +70 -0
- package/skills/ai-ml/llm-prompting/SKILL.md +71 -0
- package/skills/ai-ml/ml-model-development/SKILL.md +67 -0
- package/skills/ai-ml/multimodal-systems/SKILL.md +71 -0
- package/skills/ai-ml/retrieval-generation-fine-tuning/SKILL.md +71 -0
- package/skills/architecture/system-design/SKILL.md +67 -0
- package/skills/code-analysis/refactoring/SKILL.md +64 -0
- package/skills/code-analysis/security-vulnerability-scanning/SKILL.md +71 -0
- package/skills/code-analysis/static-analysis/SKILL.md +64 -0
- package/skills/code-generation/full-stack-application/SKILL.md +70 -0
- package/skills/code-generation/microservices-architecture/SKILL.md +71 -0
- package/skills/code-generation/python-codegen/SKILL.md +64 -0
- package/skills/code-generation/typescript-codegen/SKILL.md +64 -0
- package/skills/data-analysis/advanced-analytics/SKILL.md +71 -0
- package/skills/data-analysis/pandas-analysis/SKILL.md +66 -0
- package/skills/database-design/database-design-optimization/SKILL.md +70 -0
- package/skills/debugging/javascript-debugging/SKILL.md +67 -0
- package/skills/debugging/python-debugging/SKILL.md +67 -0
- package/skills/devops/dockerfile-creation/SKILL.md +64 -0
- package/skills/devops/kubernetes-deployment/SKILL.md +65 -0
- package/skills/documentation/api-documentation/SKILL.md +67 -0
- package/skills/error-handling/resilience-patterns/SKILL.md +70 -0
- package/skills/git-workflow/git-collaboration/SKILL.md +67 -0
- package/skills/infrastructure/cicd-pipelines/SKILL.md +70 -0
- package/skills/infrastructure/infrastructure-as-code/SKILL.md +70 -0
- package/skills/observability/monitoring-and-observability/SKILL.md +70 -0
- package/skills/performance-optimization/performance-engineering/SKILL.md +70 -0
- package/skills/prompt-optimization/prompt-engineering-advanced/SKILL.md +70 -0
- package/skills/quality-assurance/deployment-validator/SKILL.md +382 -0
- package/skills/quality-assurance/web-security-sweep/SKILL.md +320 -0
- package/skills/rag-knowledge/rag-systems/SKILL.md +70 -0
- package/skills/research/knowledge-synthesis/SKILL.md +71 -0
- package/skills/security/authentication-authorization/SKILL.md +71 -0
- package/skills/security/code-security/SKILL.md +66 -0
- package/skills/security/secure-architecture/SKILL.md +71 -0
- package/skills/self-optimization/dev-loop-optimizer/SKILL.md +344 -0
- package/skills/self-optimization/memory-learning/SKILL.md +335 -0
- package/skills/self-optimization/runtime-self-profiling/SKILL.md +250 -0
- package/skills/testing/advanced-testing-strategies/SKILL.md +71 -0
- package/skills/testing/integration-testing/SKILL.md +66 -0
- package/skills/testing/load-testing-capacity/SKILL.md +71 -0
- package/skills/testing/unit-testing/SKILL.md +66 -0
- package/skills/tool-integration/custom-tool-creation/SKILL.md +70 -0
- package/skills/web-development/advanced-frontend-patterns/SKILL.md +71 -0
- package/skills/web-development/api-design/SKILL.md +71 -0
- package/skills/web-development/css-styling/SKILL.md +67 -0
- package/skills/web-development/react-development/SKILL.md +79 -0
- package/skills/workflow-composition/workflow-orchestration/SKILL.md +70 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/*
|
|
2
|
+
LEEWAY HEADER — DO NOT REMOVE
|
|
3
|
+
|
|
4
|
+
REGION: SECURITY.AGENT.POLICY
|
|
5
|
+
TAG: SECURITY.POLICY.AGENT.MAIN
|
|
6
|
+
|
|
7
|
+
COLOR_ONION_HEX:
|
|
8
|
+
NEON=#39FF14
|
|
9
|
+
FLUO=#0DFF94
|
|
10
|
+
PASTEL=#C7FFD8
|
|
11
|
+
|
|
12
|
+
ICON_ASCII:
|
|
13
|
+
family=lucide
|
|
14
|
+
glyph=scroll
|
|
15
|
+
|
|
16
|
+
5WH:
|
|
17
|
+
WHAT = Policy agent — enforces LEEWAY policy bundles across the codebase
|
|
18
|
+
WHY = Governance requires consistent enforcement of named policy sets
|
|
19
|
+
WHO = Rapid Web Development
|
|
20
|
+
WHERE = src/agents/security/policy-agent.js
|
|
21
|
+
WHEN = 2026
|
|
22
|
+
HOW = Loads policy bundles from .leeway/policies.json, evaluates against file metadata
|
|
23
|
+
|
|
24
|
+
AGENTS:
|
|
25
|
+
POLICY
|
|
26
|
+
AUDIT
|
|
27
|
+
PERMISSION
|
|
28
|
+
|
|
29
|
+
LICENSE:
|
|
30
|
+
MIT
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readFile } from 'node:fs/promises';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
|
|
36
|
+
const DEFAULT_POLICIES = {
|
|
37
|
+
NO_SECRETS_IN_CODE: { description: 'No hardcoded secrets or credentials', enforced: true },
|
|
38
|
+
HEADERS_REQUIRED: { description: 'All code files must have LEEWAY headers', enforced: true },
|
|
39
|
+
TAGS_REQUIRED: { description: 'All code files must have valid TAGs', enforced: true },
|
|
40
|
+
NO_CIRCULAR_DEPS: { description: 'No circular module dependencies', enforced: true },
|
|
41
|
+
NAMING_CONVENTIONS: { description: 'Files must follow naming conventions', enforced: false },
|
|
42
|
+
PLACEMENT_RULES: { description: 'Files must be in correct directories', enforced: false },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* PolicyAgent enforces named LEEWAY policy bundles.
|
|
47
|
+
*/
|
|
48
|
+
export class PolicyAgent {
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
this.rootDir = options.rootDir || process.cwd();
|
|
51
|
+
this.policies = { ...DEFAULT_POLICIES };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Load policies from .leeway/policies.json (merges with defaults).
|
|
56
|
+
*/
|
|
57
|
+
async loadPolicies() {
|
|
58
|
+
const policyPath = join(this.rootDir, '.leeway', 'policies.json');
|
|
59
|
+
try {
|
|
60
|
+
const raw = await readFile(policyPath, 'utf8');
|
|
61
|
+
const loaded = JSON.parse(raw);
|
|
62
|
+
this.policies = { ...DEFAULT_POLICIES, ...loaded };
|
|
63
|
+
} catch {
|
|
64
|
+
// Use defaults
|
|
65
|
+
}
|
|
66
|
+
return this.policies;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get all currently active (enforced) policies.
|
|
71
|
+
* @returns {string[]}
|
|
72
|
+
*/
|
|
73
|
+
getActivePolicies() {
|
|
74
|
+
return Object.entries(this.policies)
|
|
75
|
+
.filter(([, p]) => p.enforced)
|
|
76
|
+
.map(([name]) => name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if a specific policy is active.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} policyName
|
|
83
|
+
* @returns {boolean}
|
|
84
|
+
*/
|
|
85
|
+
isPolicyActive(policyName) {
|
|
86
|
+
return this.policies[policyName]?.enforced === true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* List all policies with their enforcement status.
|
|
91
|
+
* @returns {object}
|
|
92
|
+
*/
|
|
93
|
+
listPolicies() {
|
|
94
|
+
return Object.entries(this.policies).map(([name, data]) => ({
|
|
95
|
+
name,
|
|
96
|
+
description: data.description,
|
|
97
|
+
enforced: data.enforced,
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/*
|
|
2
|
+
LEEWAY HEADER — DO NOT REMOVE
|
|
3
|
+
|
|
4
|
+
REGION: SECURITY.AGENT.PRIVACY
|
|
5
|
+
TAG: SECURITY.PRIVACY.AGENT.MAIN
|
|
6
|
+
|
|
7
|
+
COLOR_ONION_HEX:
|
|
8
|
+
NEON=#39FF14
|
|
9
|
+
FLUO=#0DFF94
|
|
10
|
+
PASTEL=#C7FFD8
|
|
11
|
+
|
|
12
|
+
ICON_ASCII:
|
|
13
|
+
family=lucide
|
|
14
|
+
glyph=user-x
|
|
15
|
+
|
|
16
|
+
5WH:
|
|
17
|
+
WHAT = Privacy agent — checks privacy and data handling declarations in code
|
|
18
|
+
WHY = Systems handling personal data must declare their data handling intent
|
|
19
|
+
WHO = Rapid Web Development
|
|
20
|
+
WHERE = src/agents/security/privacy-agent.js
|
|
21
|
+
WHEN = 2026
|
|
22
|
+
HOW = Scans for PII patterns in logs and data structures, checks for privacy declarations
|
|
23
|
+
|
|
24
|
+
AGENTS:
|
|
25
|
+
PRIVACY
|
|
26
|
+
POLICY
|
|
27
|
+
SECRET
|
|
28
|
+
|
|
29
|
+
LICENSE:
|
|
30
|
+
MIT
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const PII_PATTERNS = [
|
|
34
|
+
{ id: 'email_log', label: 'Email in log output', pattern: /(?:console\.|log\.|logger\.)[a-z]+\([^)]*[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-z]+/i },
|
|
35
|
+
{ id: 'phone_inline', label: 'Phone number pattern', pattern: /\b(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/ },
|
|
36
|
+
{ id: 'ssn_pattern', label: 'SSN-like pattern', pattern: /\b\d{3}-\d{2}-\d{4}\b/ },
|
|
37
|
+
{ id: 'pii_in_logs', label: 'PII field logged', pattern: /(?:log|print|console)\.[a-z]+\([^)]*(?:password|ssn|social_security|credit_card|dob|date_of_birth)/i },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* PrivacyAgent checks for privacy violations and PII exposure risks.
|
|
42
|
+
*/
|
|
43
|
+
export class PrivacyAgent {
|
|
44
|
+
constructor(options = {}) {
|
|
45
|
+
this.patterns = options.patterns || PII_PATTERNS;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Scan content for privacy violations.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} content
|
|
52
|
+
* @param {{ filePath?: string }} [options]
|
|
53
|
+
* @returns {{ compliant: boolean, findings: Finding[] }}
|
|
54
|
+
*/
|
|
55
|
+
scanContent(content, options = {}) {
|
|
56
|
+
if (!content || typeof content !== 'string') {
|
|
57
|
+
return { compliant: true, findings: [] };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const findings = [];
|
|
61
|
+
const lines = content.split('\n');
|
|
62
|
+
|
|
63
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
64
|
+
const line = lines[lineNum];
|
|
65
|
+
if (/^\s*\/\//.test(line) || /^\s*#/.test(line)) continue;
|
|
66
|
+
|
|
67
|
+
for (const { id, label, pattern } of this.patterns) {
|
|
68
|
+
if (pattern.test(line)) {
|
|
69
|
+
findings.push({
|
|
70
|
+
rule: id,
|
|
71
|
+
label,
|
|
72
|
+
line: lineNum + 1,
|
|
73
|
+
preview: line.trim().slice(0, 80),
|
|
74
|
+
file: options.filePath,
|
|
75
|
+
severity: 'high',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { compliant: findings.length === 0, findings };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
LEEWAY HEADER — DO NOT REMOVE
|
|
3
|
+
|
|
4
|
+
REGION: SECURITY.AGENT.PROMPT
|
|
5
|
+
TAG: SECURITY.SCANNER.PROMPT.MAIN
|
|
6
|
+
|
|
7
|
+
COLOR_ONION_HEX:
|
|
8
|
+
NEON=#39FF14
|
|
9
|
+
FLUO=#0DFF94
|
|
10
|
+
PASTEL=#C7FFD8
|
|
11
|
+
|
|
12
|
+
ICON_ASCII:
|
|
13
|
+
family=lucide
|
|
14
|
+
glyph=shield-alert
|
|
15
|
+
|
|
16
|
+
5WH:
|
|
17
|
+
WHAT = Prompt security agent — scans prompt files and agent instructions for unsafe patterns
|
|
18
|
+
WHY = Malicious prompt injection can compromise AI agent behavior and expose sensitive operations
|
|
19
|
+
WHO = Rapid Web Development
|
|
20
|
+
WHERE = src/agents/security/prompt-security-agent.js
|
|
21
|
+
WHEN = 2026
|
|
22
|
+
HOW = Pattern matching against known prompt injection techniques and unsafe instruction patterns
|
|
23
|
+
|
|
24
|
+
AGENTS:
|
|
25
|
+
PROMPT
|
|
26
|
+
POLICY
|
|
27
|
+
SECRET
|
|
28
|
+
|
|
29
|
+
LICENSE:
|
|
30
|
+
MIT
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readFile } from 'node:fs/promises';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
|
|
36
|
+
const INJECTION_PATTERNS = [
|
|
37
|
+
{ id: 'ignore_instructions', label: 'Ignore instructions injection', pattern: /ignore\s+(?:all\s+)?(?:previous|above)\s+instructions/i },
|
|
38
|
+
{ id: 'jailbreak_attempt', label: 'Jailbreak attempt', pattern: /(?:pretend|act as if|you are now|new mode|developer mode)/i },
|
|
39
|
+
{ id: 'data_exfil', label: 'Data exfiltration attempt', pattern: /(?:send|email|post|upload)\s+(?:all|the|your)\s+(?:data|files|secrets|passwords|keys)/i },
|
|
40
|
+
{ id: 'role_override', label: 'Role override attempt', pattern: /you\s+(?:must|shall|will)\s+(?:now\s+)?(?:act|behave|respond)\s+as/i },
|
|
41
|
+
{ id: 'system_override', label: 'System prompt override', pattern: /\[SYSTEM\]|\{\{system\}\}|<system>/i },
|
|
42
|
+
{ id: 'base64_injection', label: 'Encoded injection', pattern: /eval\s*\(\s*atob\s*\(|base64_decode\s*\(/ },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* PromptSecurityAgent scans prompt templates and agent instructions for injection risks.
|
|
47
|
+
*/
|
|
48
|
+
export class PromptSecurityAgent {
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
this.rootDir = options.rootDir || process.cwd();
|
|
51
|
+
this.patterns = options.patterns || INJECTION_PATTERNS;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Scan content for prompt injection patterns.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} content
|
|
58
|
+
* @param {{ filePath?: string }} [options]
|
|
59
|
+
* @returns {{ safe: boolean, findings: Finding[] }}
|
|
60
|
+
*/
|
|
61
|
+
scanContent(content, options = {}) {
|
|
62
|
+
if (!content || typeof content !== 'string') {
|
|
63
|
+
return { safe: true, findings: [] };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const findings = [];
|
|
67
|
+
const lines = content.split('\n');
|
|
68
|
+
|
|
69
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
70
|
+
const line = lines[lineNum];
|
|
71
|
+
for (const { id, label, pattern } of this.patterns) {
|
|
72
|
+
if (pattern.test(line)) {
|
|
73
|
+
findings.push({
|
|
74
|
+
rule: id,
|
|
75
|
+
label,
|
|
76
|
+
line: lineNum + 1,
|
|
77
|
+
preview: line.trim().slice(0, 80),
|
|
78
|
+
file: options.filePath,
|
|
79
|
+
severity: 'critical',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { safe: findings.length === 0, findings };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Scan a prompt file.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} filePath
|
|
92
|
+
* @returns {Promise<{ safe: boolean, findings: Finding[] }>}
|
|
93
|
+
*/
|
|
94
|
+
async scanFile(filePath) {
|
|
95
|
+
const fullPath = filePath.startsWith('/') ? filePath : join(this.rootDir, filePath);
|
|
96
|
+
try {
|
|
97
|
+
const content = await readFile(fullPath, 'utf8');
|
|
98
|
+
return this.scanContent(content, { filePath });
|
|
99
|
+
} catch {
|
|
100
|
+
return { safe: true, findings: [], error: 'Cannot read file' };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
LEEWAY HEADER — DO NOT REMOVE
|
|
3
|
+
|
|
4
|
+
REGION: SECURITY.AGENT.SCANNER
|
|
5
|
+
TAG: SECURITY.SCANNER.SECRET.MAIN
|
|
6
|
+
|
|
7
|
+
COLOR_ONION_HEX:
|
|
8
|
+
NEON=#39FF14
|
|
9
|
+
FLUO=#0DFF94
|
|
10
|
+
PASTEL=#C7FFD8
|
|
11
|
+
|
|
12
|
+
ICON_ASCII:
|
|
13
|
+
family=lucide
|
|
14
|
+
glyph=eye-off
|
|
15
|
+
|
|
16
|
+
5WH:
|
|
17
|
+
WHAT = Secret scan agent — looks for secrets and unsafe credential handling in source code
|
|
18
|
+
WHY = Committed secrets are a critical security vulnerability; this agent prevents them
|
|
19
|
+
WHO = Rapid Web Development
|
|
20
|
+
WHERE = src/agents/security/secret-scan-agent.js
|
|
21
|
+
WHEN = 2026
|
|
22
|
+
HOW = Pattern matching against known secret formats, env var names, and credential patterns
|
|
23
|
+
|
|
24
|
+
AGENTS:
|
|
25
|
+
SECRET
|
|
26
|
+
POLICY
|
|
27
|
+
AUDIT
|
|
28
|
+
|
|
29
|
+
LICENSE:
|
|
30
|
+
MIT
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readFile } from 'node:fs/promises';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
|
|
36
|
+
const SECRET_PATTERNS = [
|
|
37
|
+
{ id: 'api_key_inline', label: 'Inline API key', pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"`][a-zA-Z0-9\-_]{16,}['"`]/i },
|
|
38
|
+
{ id: 'password_inline', label: 'Inline password', pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"`][^'"`\s]{8,}['"`]/i },
|
|
39
|
+
{ id: 'secret_inline', label: 'Inline secret', pattern: /(?:secret|private[_-]?key)\s*[:=]\s*['"`][a-zA-Z0-9\-_+/]{16,}['"`]/i },
|
|
40
|
+
{ id: 'token_inline', label: 'Inline token', pattern: /(?:token|auth[_-]?token)\s*[:=]\s*['"`][a-zA-Z0-9\-_.]{16,}['"`]/i },
|
|
41
|
+
{ id: 'openai_key', label: 'OpenAI API key', pattern: /sk-[a-zA-Z0-9]{32,}/ },
|
|
42
|
+
{ id: 'jwt_inline', label: 'Hardcoded JWT', pattern: /eyJ[a-zA-Z0-9\-_]+\.eyJ[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+/ },
|
|
43
|
+
{ id: 'aws_key', label: 'AWS access key', pattern: /AKIA[0-9A-Z]{16}/ },
|
|
44
|
+
{ id: 'connection_string', label: 'Database connection string', pattern: /(?:mongodb|postgres|mysql|redis):\/\/[a-zA-Z0-9._%+\-]+:[a-zA-Z0-9._%+\-@]+\/[a-zA-Z0-9]+/ },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* SecretScanAgent scans source files for hardcoded secrets.
|
|
49
|
+
*/
|
|
50
|
+
export class SecretScanAgent {
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
this.rootDir = options.rootDir || process.cwd();
|
|
53
|
+
this.patterns = options.patterns || SECRET_PATTERNS;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Scan file content for secret patterns.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} content
|
|
60
|
+
* @param {{ filePath?: string }} [options]
|
|
61
|
+
* @returns {{ clean: boolean, findings: Finding[] }}
|
|
62
|
+
*/
|
|
63
|
+
scanContent(content, options = {}) {
|
|
64
|
+
if (!content || typeof content !== 'string') {
|
|
65
|
+
return { clean: true, findings: [] };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const findings = [];
|
|
69
|
+
const lines = content.split('\n');
|
|
70
|
+
|
|
71
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
72
|
+
const line = lines[lineNum];
|
|
73
|
+
|
|
74
|
+
if (/^\s*\/\//.test(line) || /^\s*#/.test(line)) continue;
|
|
75
|
+
if (/process\.env\[/.test(line) || /process\.env\./.test(line)) continue;
|
|
76
|
+
|
|
77
|
+
for (const { id, label, pattern } of this.patterns) {
|
|
78
|
+
if (pattern.test(line)) {
|
|
79
|
+
findings.push({
|
|
80
|
+
rule: id,
|
|
81
|
+
label,
|
|
82
|
+
line: lineNum + 1,
|
|
83
|
+
preview: line.trim().slice(0, 80),
|
|
84
|
+
file: options.filePath,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { clean: findings.length === 0, findings };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Scan a file for secrets.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} filePath
|
|
97
|
+
* @returns {Promise<{ clean: boolean, findings: Finding[] }>}
|
|
98
|
+
*/
|
|
99
|
+
async scanFile(filePath) {
|
|
100
|
+
const fullPath = filePath.startsWith('/') ? filePath : join(this.rootDir, filePath);
|
|
101
|
+
try {
|
|
102
|
+
const content = await readFile(fullPath, 'utf8');
|
|
103
|
+
return this.scanContent(content, { filePath });
|
|
104
|
+
} catch {
|
|
105
|
+
return { clean: true, findings: [], error: 'Cannot read file' };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*
|
|
2
|
+
LEEWAY HEADER — DO NOT REMOVE
|
|
3
|
+
|
|
4
|
+
REGION: SECURITY.AGENT.ACCESS
|
|
5
|
+
TAG: SECURITY.ACCESS.TOOL.MAIN
|
|
6
|
+
|
|
7
|
+
COLOR_ONION_HEX:
|
|
8
|
+
NEON=#39FF14
|
|
9
|
+
FLUO=#0DFF94
|
|
10
|
+
PASTEL=#C7FFD8
|
|
11
|
+
|
|
12
|
+
ICON_ASCII:
|
|
13
|
+
family=lucide
|
|
14
|
+
glyph=key
|
|
15
|
+
|
|
16
|
+
5WH:
|
|
17
|
+
WHAT = Tool access agent — ensures tools are called only through approved paths
|
|
18
|
+
WHY = Unapproved tool invocation paths create unauditable side effects and security gaps
|
|
19
|
+
WHO = Rapid Web Development
|
|
20
|
+
WHERE = src/agents/security/tool-access-agent.js
|
|
21
|
+
WHEN = 2026
|
|
22
|
+
HOW = Validates tool call paths against an approved routes registry
|
|
23
|
+
|
|
24
|
+
AGENTS:
|
|
25
|
+
TOOL
|
|
26
|
+
POLICY
|
|
27
|
+
PERMISSION
|
|
28
|
+
|
|
29
|
+
LICENSE:
|
|
30
|
+
MIT
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* ToolAccessAgent validates that tool invocations follow approved access paths.
|
|
35
|
+
*/
|
|
36
|
+
export class ToolAccessAgent {
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
this.approvedRoutes = options.approvedRoutes || new Map();
|
|
39
|
+
this.deniedRoutes = options.deniedRoutes || new Set();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Register an approved tool invocation route.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} toolName
|
|
46
|
+
* @param {string[]} allowedCallers - Agent names permitted to call this tool
|
|
47
|
+
*/
|
|
48
|
+
registerApprovedRoute(toolName, allowedCallers) {
|
|
49
|
+
this.approvedRoutes.set(toolName.toUpperCase(), allowedCallers.map(c => c.toUpperCase()));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Register a denied tool invocation route.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} toolName
|
|
56
|
+
*/
|
|
57
|
+
denyTool(toolName) {
|
|
58
|
+
this.deniedRoutes.add(toolName.toUpperCase());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if a tool invocation is allowed.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} toolName
|
|
65
|
+
* @param {string} callerAgent
|
|
66
|
+
* @returns {{ allowed: boolean, reason: string }}
|
|
67
|
+
*/
|
|
68
|
+
checkAccess(toolName, callerAgent) {
|
|
69
|
+
const tool = toolName.toUpperCase();
|
|
70
|
+
const caller = callerAgent.toUpperCase();
|
|
71
|
+
|
|
72
|
+
if (this.deniedRoutes.has(tool)) {
|
|
73
|
+
return { allowed: false, reason: `Tool "${toolName}" is explicitly denied` };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const approvedCallers = this.approvedRoutes.get(tool);
|
|
77
|
+
if (!approvedCallers) {
|
|
78
|
+
return { allowed: true, reason: `Tool "${toolName}" has no access restrictions` };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (approvedCallers.includes('*') || approvedCallers.includes(caller)) {
|
|
82
|
+
return { allowed: true, reason: `Caller "${callerAgent}" is approved for tool "${toolName}"` };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
allowed: false,
|
|
87
|
+
reason: `Caller "${callerAgent}" is not in approved list for tool "${toolName}": [${approvedCallers.join(', ')}]`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validate a batch of tool invocations.
|
|
93
|
+
*
|
|
94
|
+
* @param {{ tool: string, caller: string }[]} invocations
|
|
95
|
+
* @returns {{ valid: boolean, violations: object[] }}
|
|
96
|
+
*/
|
|
97
|
+
validateBatch(invocations) {
|
|
98
|
+
const violations = [];
|
|
99
|
+
for (const { tool, caller } of invocations) {
|
|
100
|
+
const result = this.checkAccess(tool, caller);
|
|
101
|
+
if (!result.allowed) violations.push({ tool, caller, reason: result.reason });
|
|
102
|
+
}
|
|
103
|
+
return { valid: violations.length === 0, violations };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
LEEWAY HEADER — DO NOT REMOVE
|
|
3
|
+
|
|
4
|
+
REGION: AI.AGENT.STANDARDS
|
|
5
|
+
TAG: AI.AGENT.AUTHORITY.MAIN
|
|
6
|
+
|
|
7
|
+
COLOR_ONION_HEX:
|
|
8
|
+
NEON=#39FF14
|
|
9
|
+
FLUO=#0DFF94
|
|
10
|
+
PASTEL=#C7FFD8
|
|
11
|
+
|
|
12
|
+
ICON_ASCII:
|
|
13
|
+
family=lucide
|
|
14
|
+
glyph=shield
|
|
15
|
+
|
|
16
|
+
5WH:
|
|
17
|
+
WHAT = Authority agent — declares allowed actions, denied actions, and execution scope per file/module
|
|
18
|
+
WHY = Governance requires explicit declaration of what each file is permitted to do
|
|
19
|
+
WHO = Rapid Web Development
|
|
20
|
+
WHERE = src/agents/standards/authority-agent.js
|
|
21
|
+
WHEN = 2026
|
|
22
|
+
HOW = Reads LEEWAY headers and .leeway/authority.json to validate action permissions
|
|
23
|
+
|
|
24
|
+
AGENTS:
|
|
25
|
+
AUTHORITY
|
|
26
|
+
POLICY
|
|
27
|
+
AUDIT
|
|
28
|
+
|
|
29
|
+
LICENSE:
|
|
30
|
+
MIT
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readFile } from 'node:fs/promises';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
import { parseHeader } from '../../core/header-parser.js';
|
|
36
|
+
|
|
37
|
+
const DEFAULT_AUTHORITY = {
|
|
38
|
+
allowedActions: ['read', 'write', 'transform', 'export'],
|
|
39
|
+
deniedActions: ['execute_shell', 'network_request', 'modify_secrets', 'delete_files'],
|
|
40
|
+
executionScope: 'module',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* AuthorityAgent declares and validates what actions a file or module is permitted to perform.
|
|
45
|
+
*/
|
|
46
|
+
export class AuthorityAgent {
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
this.rootDir = options.rootDir || process.cwd();
|
|
49
|
+
this.authorityConfig = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Load the project-level authority configuration.
|
|
54
|
+
*/
|
|
55
|
+
async loadConfig() {
|
|
56
|
+
const configPath = join(this.rootDir, '.leeway', 'authority.json');
|
|
57
|
+
try {
|
|
58
|
+
const raw = await readFile(configPath, 'utf8');
|
|
59
|
+
this.authorityConfig = JSON.parse(raw);
|
|
60
|
+
} catch {
|
|
61
|
+
this.authorityConfig = { default: DEFAULT_AUTHORITY };
|
|
62
|
+
}
|
|
63
|
+
return this.authorityConfig;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get the authority declaration for a file.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} filePath
|
|
70
|
+
* @returns {Promise<AuthorityDeclaration>}
|
|
71
|
+
*/
|
|
72
|
+
async getFileAuthority(filePath) {
|
|
73
|
+
if (!this.authorityConfig) await this.loadConfig();
|
|
74
|
+
|
|
75
|
+
const fullPath = filePath.startsWith('/') ? filePath : join(this.rootDir, filePath);
|
|
76
|
+
let content = '';
|
|
77
|
+
try {
|
|
78
|
+
content = await readFile(fullPath, 'utf8');
|
|
79
|
+
} catch {
|
|
80
|
+
return { file: filePath, status: 'error' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const header = parseHeader(content);
|
|
84
|
+
const region = header?.region?.split('.')?.[0] || 'UNKNOWN';
|
|
85
|
+
|
|
86
|
+
const authority = this.authorityConfig[region]
|
|
87
|
+
|| this.authorityConfig['default']
|
|
88
|
+
|| DEFAULT_AUTHORITY;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
file: filePath,
|
|
92
|
+
region,
|
|
93
|
+
agents: header?.agents || [],
|
|
94
|
+
authority,
|
|
95
|
+
status: 'resolved',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if a specific action is allowed for a file.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} filePath
|
|
103
|
+
* @param {string} action
|
|
104
|
+
* @returns {Promise<boolean>}
|
|
105
|
+
*/
|
|
106
|
+
async isActionAllowed(filePath, action) {
|
|
107
|
+
const decl = await this.getFileAuthority(filePath);
|
|
108
|
+
if (decl.status !== 'resolved') return false;
|
|
109
|
+
|
|
110
|
+
const { allowedActions, deniedActions } = decl.authority;
|
|
111
|
+
if (deniedActions.includes(action)) return false;
|
|
112
|
+
return allowedActions.includes(action) || allowedActions.includes('*');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
LEEWAY HEADER — DO NOT REMOVE
|
|
3
|
+
|
|
4
|
+
REGION: AI.AGENT.STANDARDS
|
|
5
|
+
TAG: AI.AGENT.DISCOVERY.PIPELINE
|
|
6
|
+
|
|
7
|
+
COLOR_ONION_HEX:
|
|
8
|
+
NEON=#39FF14
|
|
9
|
+
FLUO=#0DFF94
|
|
10
|
+
PASTEL=#C7FFD8
|
|
11
|
+
|
|
12
|
+
ICON_ASCII:
|
|
13
|
+
family=lucide
|
|
14
|
+
glyph=git-merge
|
|
15
|
+
|
|
16
|
+
5WH:
|
|
17
|
+
WHAT = Discovery pipeline agent — ensures every artifact has the discovery chain metadata
|
|
18
|
+
WHY = LEEWAY applications must be visible to search engines, AI tools, and voice assistants
|
|
19
|
+
WHO = Rapid Web Development
|
|
20
|
+
WHERE = src/agents/standards/discovery-pipeline-agent.js
|
|
21
|
+
WHEN = 2026
|
|
22
|
+
HOW = Checks for Schema.org JSON-LD, OpenGraph, sitemap, and FAQ structured data presence
|
|
23
|
+
|
|
24
|
+
AGENTS:
|
|
25
|
+
DISCOVERY
|
|
26
|
+
SCHEMA
|
|
27
|
+
SITEMAP
|
|
28
|
+
|
|
29
|
+
LICENSE:
|
|
30
|
+
MIT
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readFile } from 'node:fs/promises';
|
|
34
|
+
import { join, extname } from 'node:path';
|
|
35
|
+
|
|
36
|
+
const DISCOVERY_CHECKS = [
|
|
37
|
+
{ id: 'jsonld', label: 'Schema.org JSON-LD', pattern: /<script[^>]+type="application\/ld\+json"/i },
|
|
38
|
+
{ id: 'opengraph', label: 'OpenGraph meta tags', pattern: /<meta[^>]+property="og:/i },
|
|
39
|
+
{ id: 'twitter_card', label: 'Twitter Card meta', pattern: /<meta[^>]+name="twitter:/i },
|
|
40
|
+
{ id: 'canonical', label: 'Canonical URL', pattern: /<link[^>]+rel="canonical"/i },
|
|
41
|
+
{ id: 'faq', label: 'FAQ structured data', pattern: /"@type"\s*:\s*"FAQPage"/i },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* DiscoveryPipelineAgent checks that HTML/JSX files include required discovery metadata.
|
|
46
|
+
*/
|
|
47
|
+
export class DiscoveryPipelineAgent {
|
|
48
|
+
constructor(options = {}) {
|
|
49
|
+
this.rootDir = options.rootDir || process.cwd();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check a file for discovery chain completeness.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} filePath
|
|
56
|
+
* @returns {Promise<DiscoveryCheckResult>}
|
|
57
|
+
*/
|
|
58
|
+
async checkFile(filePath) {
|
|
59
|
+
const ext = extname(filePath).toLowerCase();
|
|
60
|
+
const htmlLike = ['.html', '.htm', '.jsx', '.tsx', '.vue', '.svelte'];
|
|
61
|
+
|
|
62
|
+
if (!htmlLike.includes(ext)) {
|
|
63
|
+
return { file: filePath, applicable: false, reason: 'Not an HTML-like file' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const fullPath = filePath.startsWith('/') ? filePath : join(this.rootDir, filePath);
|
|
67
|
+
let content = '';
|
|
68
|
+
try {
|
|
69
|
+
content = await readFile(fullPath, 'utf8');
|
|
70
|
+
} catch {
|
|
71
|
+
return { file: filePath, applicable: true, status: 'error' };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const checks = DISCOVERY_CHECKS.map(check => ({
|
|
75
|
+
...check,
|
|
76
|
+
present: check.pattern.test(content),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
const presentCount = checks.filter(c => c.present).length;
|
|
80
|
+
const score = Math.round((presentCount / DISCOVERY_CHECKS.length) * 100);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
file: filePath,
|
|
84
|
+
applicable: true,
|
|
85
|
+
score,
|
|
86
|
+
checks,
|
|
87
|
+
missing: checks.filter(c => !c.present).map(c => c.label),
|
|
88
|
+
status: score === 100 ? 'complete' : score >= 60 ? 'partial' : 'incomplete',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|