@yesprasad/fluent-graph 0.2.3 → 0.2.5
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/README.md +79 -0
- package/dist/cli/index.js +30 -25
- package/dist/index.d.ts +120 -1
- package/dist/index.js +1 -1
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -118,6 +118,82 @@ Target: x_1566039_seventh_customer
|
|
|
118
118
|
|
|
119
119
|
---
|
|
120
120
|
|
|
121
|
+
### Validate — catch structural issues before deploy
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
fluent-graph validate
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Runs a set of structural checks across the whole project and reports issues by severity:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
🔍 STRUCTURAL VALIDATION
|
|
131
|
+
════════════════════════════════════════════
|
|
132
|
+
|
|
133
|
+
ERROR [FG-001] "cs_incident_onload" references field "priorty" on table
|
|
134
|
+
"incident" but no such field exists
|
|
135
|
+
File: client-scripts/cs-incident-onload.now.ts
|
|
136
|
+
Suggestion: Did you mean "priority"?
|
|
137
|
+
|
|
138
|
+
WARNING [FG-002] cs_a and cs_b (order: 100) both fire "onChange" on
|
|
139
|
+
"incident" — nondeterministic execution order
|
|
140
|
+
|
|
141
|
+
════════════════════════════════════════════
|
|
142
|
+
1 error, 1 warning
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Checks include:
|
|
146
|
+
- **FG-001** — script references a field that doesn't exist on the target table (with Levenshtein-based "did you mean" suggestions)
|
|
147
|
+
- **FG-002** — two scripts on the same table/event/order, so execution order is nondeterministic
|
|
148
|
+
- **FG-003** — a reference points to a table that isn't in the local project or known platform tables
|
|
149
|
+
- **FG-004** — two artifacts with the same name on the same table — the last one defined silently wins
|
|
150
|
+
- **FG-005** — an artifact is marked for deletion but other active artifacts still depend on it
|
|
151
|
+
|
|
152
|
+
`fluent-graph validate` exits non-zero on errors, so it's a natural CI gate:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
fluent-graph validate --severity error
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Use `--format json` for machine-readable output and `--severity <error|warning|info>` to control the noise floor.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### Context — minimal, AI-ready context for a table
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
fluent-graph context <table_name>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Extracts just what an LLM (or a human) needs to reason about one table — its fields, attached scripts, business rules, what depends on it, what it depends on, and a blast-radius summary — instead of dumping the entire `fluent-graph.json`:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
🧠 CONTEXT FOR: incident
|
|
172
|
+
════════════════════════════════════════════════
|
|
173
|
+
|
|
174
|
+
📋 Fields (3)
|
|
175
|
+
📋 Attached scripts (4)
|
|
176
|
+
🔗 Dependents (2 tables reference incident)
|
|
177
|
+
↗ References from incident (1)
|
|
178
|
+
🔥 Blast radius: 4 direct, 2 transitive — MEDIUM
|
|
179
|
+
|
|
180
|
+
📊 TOKEN METRICS
|
|
181
|
+
This context 1,204 tokens
|
|
182
|
+
Full graph 18,340 tokens
|
|
183
|
+
Saved 17,136 tokens
|
|
184
|
+
Reduction 93%
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
This is meant to be piped straight into an AI coding assistant or agent prompt — `fluent-graph context incident --format json` gives you a compact, structured payload instead of pasting the full graph and burning context window.
|
|
188
|
+
|
|
189
|
+
Options:
|
|
190
|
+
- `--depth <n>` — how many hops of dependents/references to include (default 1)
|
|
191
|
+
- `--no-fields` / `--no-scripts` / `--no-blast` — trim sections you don't need
|
|
192
|
+
- `--show-metrics` — see the per-section token breakdown and the exact JSON payload that would be sent to an LLM
|
|
193
|
+
- `-f, --format <table|json>`
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
121
197
|
## CI/CD Integration
|
|
122
198
|
|
|
123
199
|
```yaml
|
|
@@ -125,6 +201,9 @@ Target: x_1566039_seventh_customer
|
|
|
125
201
|
- name: Analyze lineage
|
|
126
202
|
run: npx @yesprasad/fluent-graph analyze
|
|
127
203
|
|
|
204
|
+
- name: Validate structural issues
|
|
205
|
+
run: npx @yesprasad/fluent-graph validate --severity error
|
|
206
|
+
|
|
128
207
|
- name: Fail on identity conflicts
|
|
129
208
|
run: |
|
|
130
209
|
if grep -q '"action": "CONFLICT"' fluent-graph.json; then
|
package/dist/cli/index.js
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
\u{1F4CA} LINEAGE MAP`)),console.log(
|
|
3
|
-
${
|
|
4
|
-
\u{1F517} SCHEMA RELATIONSHIPS (Reference Fields)`));let
|
|
5
|
-
\u{1F9E0} PROJECT INSIGHTS`));let
|
|
6
|
-
`)}var
|
|
7
|
-
\u274C Error:`),
|
|
8
|
-
\u{1F4A1} Make sure you're in a ServiceNow Fluent project directory`))),process.exit(1)),
|
|
9
|
-
\u2705 Analysis complete`)),console.log(
|
|
10
|
-
\u274C Error:`),r.message),process.env.DEBUG&&(console.error(
|
|
11
|
-
Stack trace:`)),console.error(r.stack))):(console.error(
|
|
12
|
-
\u274C Unknown error occurred`)),console.error(r));process.exit(1)}});var
|
|
13
|
-
\u274C Error: Graph file not found at ${r}`)),process.exit(1));let
|
|
14
|
-
\u274C Target "${
|
|
15
|
-
\u{1F4A1} Available custom tables:`)),
|
|
16
|
-
\u{1F4A1} Referenced platform tables:`)),
|
|
17
|
-
\u{1F525} BLAST RADIUS ANALYSIS`)),console.log(
|
|
1
|
+
"use strict";var $e=Object.create;var oe=Object.defineProperty;var Ge=Object.getOwnPropertyDescriptor;var Pe=Object.getOwnPropertyNames;var Fe=Object.getPrototypeOf,Ve=Object.prototype.hasOwnProperty;var Me=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of Pe(e))!Ve.call(t,o)&&o!==n&&oe(t,o,{get:()=>e[o],enumerable:!(r=Ge(e,o))||r.enumerable});return t};var T=(t,e,n)=>(n=t!=null?$e(Fe(t)):{},Me(e||!t||!t.__esModule?oe(n,"default",{value:t,enumerable:!0}):n,t));var xe=require("commander"),Le=T(require("path")),Ce=T(require("fs"));var ye=require("commander"),Z=T(require("path")),D=T(require("chalk")),W=T(require("ora"));var Y=T(require("path")),X=T(require("fs")),C=async t=>{let e=Y.default.join(t,"node_modules");if(!X.default.existsSync(e))return{found:!1,error:"node_modules not found. Run npm install first."};let n=Y.default.join(e,"@servicenow","sdk");return X.default.existsSync(n)?{found:!0,sdkPath:n}:{found:!1,error:"@servicenow/sdk not found. This is not a ServiceNow Fluent project."}};var ie=require("module"),ae=T(require("path"));var re=require("module"),se=T(require("path")),F=class{constructor(){this.tables=new Map}load(e){try{let r=(0,re.createRequire)(se.default.join(e,"package.json"))("@servicenow/sdk-build-plugins");for(let[,o]of Object.entries(r)){let s=o;if(!s?.config?.name)continue;let a=s.config.name,l=Object.keys(s.config.records||{}).filter(i=>i!=="*");if(l.length!==0){for(let i of l)this.register(i,a);try{let i=s.getRelationships();i&&typeof i=="object"&&this.parseRelationships(i,a)}catch{}}}}catch{}}register(e,n){this.tables.has(e)||this.tables.set(e,{table:e,pluginName:n,isChild:!1,children:[]})}parseRelationships(e,n){for(let[r,o]of Object.entries(e))this.register(r,n),o&&typeof o=="object"&&this.processChildren(o,n,r)}processChildren(e,n,r){for(let[o,s]of Object.entries(e)){if(o==="via"||o==="descendant"||o==="relationships"||o===r)continue;let a=o;this.register(a,n);let l=this.tables.get(a);l.isChild||(l.isChild=!0,l.parentTable=r,s&&typeof s=="object"&&typeof s.via=="string"&&(l.parentVia=s.via));let i=this.tables.get(r);i&&!i.children.includes(a)&&i.children.push(a),s&&typeof s=="object"&&s.relationships&&typeof s.relationships=="object"&&this.processChildren(s.relationships,n,a)}}get(e){return this.tables.get(e)}getPluginName(e){return this.tables.get(e)?.pluginName??"Record"}getDisplayGroupName(e){let n=this.tables.get(e);return n?n.pluginName.replace(/Plugin$/,"").replace(/([a-z])([A-Z])/g,"$1 $2").replace(/^./,r=>r.toUpperCase()).trim():"Other"}isChild(e){return this.tables.get(e)?.isChild??!1}getParentTable(e){return this.tables.get(e)?.parentTable}getChildren(e){return this.tables.get(e)?.children??[]}getTablesByPlugin(e){return[...this.tables.values()].filter(n=>n.pluginName===e).map(n=>n.table)}getAllPluginNames(){return[...new Set([...this.tables.values()].map(e=>e.pluginName))].sort()}getAllTables(){return[...this.tables.keys()]}isKnown(e){return this.tables.has(e)}};function V(t){let e=(0,ie.createRequire)(ae.default.join(t,"package.json")),n=new F;return n.load(t),{buildCore:e("@servicenow/sdk-build-core"),sdkapi:e("@servicenow/sdk-api"),registry:n}}var ce=T(require("path"));function je(t){let e={};for(let[n,r]of Object.entries(t))if(r)try{let o=typeof r.getValue=="function"?r.getValue():r;(typeof o!="object"||o===null||Array.isArray(o))&&(e[n]=o)}catch{continue}return e}function le(t,e){let n=[];for(let r of t.query()){let o=r.setProperties||{},s=r.getId()?.getValue()?.toString()||"",a=r.getTable()?.toString()||"",l=o.name?.getValue?.()?.toString()||o.label?.getValue?.()?.toString()||o.sys_name?.getValue?.()?.toString()||s,i=r.getOriginalFilePath()?.toString()||"",c=i?ce.default.relative(e,i):"";n.push({id:s,table:a,action:r.getAction(),name:l,attributes:je(o),source:{file:c,type:r.getOriginalSource()?.getKindName?.()||"SourceFile"}})}return n}function de(t,e,n){let r=[];for(let o of t.query()){let s=o.getId()?.getValue()?.toString(),a=o.setProperties||{},l=o.getTable()?.toString(),i=a.internal_type?.getValue?.()?.toString(),c=a.reference?.getValue?.()?.toString(),m=a.element?.getValue?.()?.toString();if(i==="reference"&&c){let h=e.find(d=>d.name===c);r.push({from:s,to:h?.id||`platform:${c}`,via:m||"reference",type:"schema_relationship"})}let v=a.table?.getValue?.()?.toString()||a.collection?.getValue?.()?.toString();if(v){let h=e.find(d=>d.name===v);r.push({from:s,to:h?.id||`platform:${v}`,via:a.table?"table":"collection",type:"dependency",targetTable:v})}if(n.getPluginName(l)==="AclPlugin"){let h=a.name?.getValue?.()?.toString();if(h&&h.includes(".")){let[d]=h.split("."),g=e.find(w=>w.name===d);r.push({from:s,to:g?.id||`platform:${d}`,via:"name",type:"dependency",targetTable:d})}}for(let[h,d]of Object.entries(a))if(Array.isArray(d))d.forEach(g=>{if(g&&g.constructor?.name==="RecordId"){let w=g.getValue?.()?.toString(),N=g.getTable?.()?.toString();w&&n.getPluginName(N)!=="TablePlugin"&&r.push({from:s,to:w,via:h,type:"dependency",targetTable:N})}});else if(d&&d.constructor?.name==="RecordId"){let g=d.getValue?.()?.toString(),w=d.getTable?.()?.toString();g&&n.getPluginName(w)!=="TablePlugin"&&r.push({from:s,to:g,via:h,type:"dependency",targetTable:w})}else if(d&&d.constructor?.name==="Record"){let g=d.getId?.()?.getValue?.()?.toString();if(g){let w=e.find(N=>N.id===g);w&&n.getPluginName(w.table)!=="TablePlugin"&&r.push({from:s,to:g,via:h,type:"dependency",targetTable:w.table})}}}return r}function fe(t){let e=[];for(let n of t.query()){let r=n.getId()?.getValue()?.toString(),o=n.getRelated?.()||[];for(let s of o){let a=s.getId()?.getValue()?.toString();a&&r&&e.push({from:r,to:a,type:"composition"})}}return e}function ge(t,e,n,r){let o=t.getConfig()||{},s=t.getPackage()||{};return{projectRoot:t.getRootDir()||process.cwd(),scopeId:o.scopeId||"unknown",scopeName:o.scope||"Global",recordCount:e,referenceCount:n,compositionCount:r,generatedAt:new Date().toISOString(),sdkVersion:s.dependencies?.["@servicenow/sdk"]||s.devDependencies?.["@servicenow/sdk"]||"unknown"}}async function M(t,e){let n=await t.getRecords(),r=t.getRootDir(),o=le(n,r),s=de(n,o,e),a=fe(n),l=[...s,...a];return{metadata:ge(t,n.query().length,s.length,a.length),nodes:o,edges:l}}var _=T(require("fs")),pe=T(require("path"));function ue(t,e){let n=JSON.stringify(t,null,2),r=pe.default.dirname(e);_.default.existsSync(r)||_.default.mkdirSync(r,{recursive:!0}),_.default.writeFileSync(e,n,"utf-8")}var me=T(require("path")),b=T(require("chalk")),J=T(require("cli-table3"));function he(t,e){console.log(b.default.yellow.bold(`
|
|
2
|
+
\u{1F4CA} LINEAGE MAP`)),console.log(b.default.gray("\u2550".repeat(80)));let n=new Map;for(let i of t.nodes){if(i.action==="DELETE")continue;let c=e.getDisplayGroupName(i.table);n.has(c)||n.set(c,[]),n.get(c).push(i)}for(let[i,c]of n){let m=c.filter(h=>!e.isChild(h.table)&&h.source.file);if(m.length===0)continue;if(console.log(b.default.cyan.bold(`
|
|
3
|
+
${i}`)),m.some(h=>e.getPluginName(h.table)==="BusinessRulePlugin")){let h=new J.default({head:[b.default.cyan("Name"),b.default.cyan("When"),b.default.cyan("Table"),b.default.cyan("On"),b.default.cyan("Order"),b.default.cyan("Status"),b.default.cyan("File")],colWidths:[32,8,28,16,7,8,28],wordWrap:!0,style:{head:[],border:["gray"]}});m.forEach(d=>{let g=d.action==="DELETE"?b.default.red("DELETE"):b.default.green("ACTIVE"),w=d.attributes.when||"",p=t.edges.find(y=>y.from===d.id&&y.type==="dependency"&&y.targetTable)?.targetTable||"",S=[];d.attributes.action_insert&&S.push("Insert"),d.attributes.action_update&&S.push("Update"),d.attributes.action_delete&&S.push("Delete"),d.attributes.action_query&&S.push("Query"),h.push([b.default.white(d.name),b.default.yellow(w),b.default.magenta(p),S.length?S.join(", "):b.default.gray("\u2014"),String(d.attributes.order??""),g,b.default.gray(me.default.basename(d.source.file))])}),console.log(h.toString())}else{let h=new J.default({head:[b.default.cyan("Name"),b.default.cyan("Status"),b.default.cyan("Source File")],wordWrap:!0,style:{head:[],border:["gray"]}});m.forEach(d=>{let g=d.action==="DELETE"?b.default.red("DELETE"):b.default.green("ACTIVE");h.push([b.default.white(d.name),g,b.default.gray(d.source.file)])}),console.log(h.toString())}}let r=t.edges.filter(i=>i.type==="schema_relationship");if(r.length>0){console.log(b.default.cyan.bold(`
|
|
4
|
+
\u{1F517} SCHEMA RELATIONSHIPS (Reference Fields)`));let i=new J.default({head:[b.default.cyan("Field"),b.default.cyan("From Table"),b.default.cyan("Target Table")],wordWrap:!0,style:{head:[],border:["gray"]}});r.forEach(c=>{let m=t.nodes.find(g=>g.id===c.from),v=m?.attributes?.element||c.via||"unknown",h=m?.attributes?.name||"unknown",d=c.to.startsWith("platform:")?c.to.split(":")[1]:t.nodes.find(g=>g.id===c.to)?.name||c.to;i.push([b.default.white(v),b.default.gray(h),b.default.magenta(d)])}),console.log(i.toString())}console.log(b.default.cyan.bold(`
|
|
5
|
+
\u{1F9E0} PROJECT INSIGHTS`));let o=t.edges.filter(i=>i.to.startsWith("platform:")).length,s=t.edges.filter(i=>!i.to.startsWith("platform:")).length,a=t.nodes.filter(i=>i.action!=="DELETE"&&i.source.file&&!i.id.startsWith("platform:")&&!e.isChild(i.table));console.log(` \u2022 Total User Artifacts: ${b.default.cyan(a.length)}`);let l=a.reduce((i,c)=>{let m=e.getDisplayGroupName(c.table);return i[m]=(i[m]||0)+1,i},{});console.log(` \u2022 User Artifacts Breakdown: ${JSON.stringify(l,null,2)}`),console.log(` \u2022 Platform Dependencies: ${b.default.magenta(o)}`),console.log(` \u2022 Internal References: ${b.default.white(s)}`),console.log(` \u2022 Total Artifacts: ${b.default.cyan(t.nodes.length)}`),console.log(` \u2022 Total Relationships: ${b.default.cyan(t.edges.length)}`),console.log(b.default.gray("\u2550".repeat(80))+`
|
|
6
|
+
`)}var be=new ye.Command("analyze").description("Analyze and extract dependency graph from the current project").option("-o, --output <path>","Output file path","fluent-graph.json").option("-f, --format <type>","Output format: table, json","table").option("-q, --quiet","Suppress console output").option("--no-display","Skip visual display").option("--no-banner","Skip banner (inherited)").action(async t=>{let e=t.format==="json",n;try{let r=process.cwd();!t.quiet&&!e&&(n=(0,W.default)("Detecting ServiceNow SDK...").start());let o=await C(r);o.found||(n&&n.fail("SDK not found"),e?console.log(JSON.stringify({error:o.error,code:"SDK_NOT_FOUND"})):(console.error(D.default.red(`
|
|
7
|
+
\u274C Error:`),o.error),console.log(D.default.yellow(`
|
|
8
|
+
\u{1F4A1} Make sure you're in a ServiceNow Fluent project directory`))),process.exit(1)),n&&n.succeed(`\u2705 SDK detected at ${D.default.gray(o.sdkPath)}`),!t.quiet&&!e&&(n=(0,W.default)("Loading workspace SDK...").start());let s=V(o.sdkPath);n&&n.succeed("Workspace SDK loaded"),!t.quiet&&!e&&(n=(0,W.default)("Loading project configuration...").start());let a=new s.sdkapi.Project({rootDir:r});n&&n.succeed("Project loaded"),!t.quiet&&!e&&(n=(0,W.default)("Building dependency graph...").start());let l;if(e){let m=process.stdout.write.bind(process.stdout);process.stdout.write=process.stderr.write.bind(process.stderr),l=()=>{process.stdout.write=m}}let i=await M(a,s.registry);l?.(),n&&n.succeed(`Graph built: ${D.default.cyan(i.nodes.length)} nodes, ${D.default.cyan(i.edges.length)} edges`);let c=Z.default.isAbsolute(t.output)?t.output:Z.default.join(r,t.output);ue(i,c),e?console.log(JSON.stringify(i,null,2)):(t.quiet||(console.log(D.default.green(`
|
|
9
|
+
\u2705 Analysis complete`)),console.log(D.default.gray(" Location: ")+D.default.white(c)),console.log(D.default.gray(" Complexity: ")+D.default.cyan((i.edges.length/i.nodes.length).toFixed(2))+D.default.gray(" edges/node")),console.log(D.default.gray(" Generated at: ")+D.default.gray(new Date().toLocaleTimeString()))),t.display&&!t.quiet&&he(i,s.registry))}catch(r){if(e){let o=r instanceof Error?r.message:"Unexpected error";console.log(JSON.stringify({error:o,code:"EXTRACTION_FAILED"}))}else r instanceof Error?(console.error(D.default.red(`
|
|
10
|
+
\u274C Error:`),r.message),process.env.DEBUG&&(console.error(D.default.gray(`
|
|
11
|
+
Stack trace:`)),console.error(r.stack))):(console.error(D.default.red(`
|
|
12
|
+
\u274C Unknown error occurred`)),console.error(r));process.exit(1)}});var we=require("commander"),B=T(require("fs")),H=T(require("path")),f=T(require("chalk")),Q=T(require("cli-table3"));function Ke(t){let e=new Map;for(let n of t.nodes){let r=e.get(n.name)??[];r.push(n.id),e.set(n.name,r)}return e}function We(t){let e=new Map,n=new Map,r=new Map;return t.edges.forEach((o,s)=>{r.set(o,s);let a=e.get(o.to)??[];if(a.push(o),e.set(o.to,a),o.targetTable){let l=n.get(o.targetTable)??[];l.push(o),n.set(o.targetTable,l)}}),{byNodeId:e,byTargetTable:n,originalIndexByEdge:r}}function _e(t,e,n){let{byNodeId:r,byTargetTable:o,originalIndexByEdge:s}=t,a=new Set(r.get(e)??[]);if(n.length>0)for(let l of o.get(n)??[])a.add(l);return[...a].sort((l,i)=>s.get(l)-s.get(i))}function j(t,e,n){let r=Math.min(n,25),o=Ke(t),s=We(t),a=new Map(t.nodes.map(p=>[p.id,p])),l=t.nodes.filter(p=>p.name===e),i=l[0]??null,c=new Set,m=`platform:${e}`;c.add(m);for(let p of l)c.add(p.id);let h=[...new Set([m,...l.map(p=>p.id)])].map(p=>({nodeId:p,nodeName:e,hop:0,path:[e]})),d=[],g=new Set([e]),w=0,N=0;for(;h.length>0;){let p=[];for(let S of h){if(S.hop>=r)continue;let y=_e(s,S.nodeId,S.nodeName);for(let A of y){let E=a.get(A.from);if(!E)continue;if(c.has(E.id)){N++;continue}c.add(E.id);let I=o.get(E.name)??[];for(let $ of I)c.has($)||c.add($);let R=S.hop+1,x=[...S.path,E.name];g.has(E.name)||(g.add(E.name),d.push({node:E,hop:R,path:x,viaEdge:A}));for(let $ of I)p.push({nodeId:$,nodeName:E.name,hop:R,path:x});R>w&&(w=R)}}h=p}return d.sort((p,S)=>p.hop-S.hop||p.node.name.localeCompare(S.node.name)),{target:i,targetName:e,hops:d,maxHopReached:w,cyclesPrevented:N}}var Se=new we.Command("blast").description("Analyze blast radius for a target table").argument("<target>","Target table name (e.g., incident, x_bank_account)").option("-g, --graph <path>","Path to graph JSON file","fluent-graph.json").option("-f, --format <type>","Output format: table, json, csv","table").option("--depth <n>","Maximum traversal depth","10").option("--show-paths","Show the full dependency chain for each transitive artifact").option("--direct-only","Show only hop-1 (direct) impacts").option("--include-deleted","Include deleted artifacts in the analysis").option("--no-banner","Skip banner (inherited)").action(async(t,e)=>{let n=e.format==="json";try{let r=H.isAbsolute(e.graph)?e.graph:H.join(process.cwd(),e.graph),o=new F,s=await C(process.cwd());s.found&&s.sdkPath&&o.load(s.sdkPath),B.existsSync(r)||(n?console.log(JSON.stringify({error:`Graph file not found at ${r}`,code:"GRAPH_NOT_FOUND"})):console.error(f.default.red(`
|
|
13
|
+
\u274C Error: Graph file not found at ${r}`)),process.exit(1));let a=JSON.parse(B.readFileSync(r,"utf8")),l=a.nodes.find(p=>p.name===t),i=l?l.id:`platform:${t}`,c=!l,m=a.edges.some(p=>p.to===i||p.targetTable===t);if(!l&&!m){let p=a.nodes.filter(y=>o.getPluginName(y.table)==="TablePlugin"&&!o.isChild(y.table)&&y.action!=="DELETE").map(y=>y.name).sort(),S=[...new Set(a.edges.filter(y=>y.to.startsWith("platform:")).map(y=>y.to.split(":")[1]))].sort();n?console.log(JSON.stringify({error:`Target "${t}" not found or referenced`,code:"TARGET_NOT_FOUND",availableCustomTables:p,availablePlatformTables:S})):(console.log(f.default.red(`
|
|
14
|
+
\u274C Target "${t}" not found or referenced`)),console.log(f.default.yellow(`
|
|
15
|
+
\u{1F4A1} Available custom tables:`)),p.forEach(y=>console.log(f.default.gray(" \u2022 ")+f.default.white(y))),console.log(f.default.yellow(`
|
|
16
|
+
\u{1F4A1} Referenced platform tables:`)),S.forEach(y=>console.log(f.default.gray(" \u2022 ")+f.default.magenta(y)))),process.exit(1)}n||(console.log(f.default.yellow.bold(`
|
|
17
|
+
\u{1F525} BLAST RADIUS ANALYSIS`)),console.log(f.default.gray("\u2550".repeat(80))),console.log(f.default.gray("Target: ")+f.default.white(t)+(c?f.default.yellow(" [Base Table]"):"")),c||console.log(f.default.gray("File: ")+f.default.gray(l.source.file)),console.log(f.default.gray("\u2550".repeat(80))));let v=e.directOnly?1:Math.max(1,parseInt(e.depth,10)||10),h=j(a,t,v),d=p=>!e.includeDeleted&&p.node.action==="DELETE"?!1:p.node.source.file?!0:!o.isChild(p.node.table),g=h.hops.filter(d),w=g.filter(p=>p.hop===1),N=g.filter(p=>p.hop>1);if(g.length===0){console.log(n?JSON.stringify({target:t,isBaseTable:c,totalImpacted:0,direct:0,transitive:0,maxHopReached:0,cyclesPrevented:h.cyclesPrevented,riskLevel:"LOW",impacts:[]},null,2):f.default.green(`
|
|
18
18
|
\u2705 Zero dependencies detected in local project.
|
|
19
|
-
`));return}switch(e.format){case"json":{let g
|
|
20
|
-
\u26A1 DIRECT IMPACT`)+
|
|
21
|
-
\u{1F30A} TRANSITIVE IMPACT`)+
|
|
22
|
-
\u{1F4CA} IMPACT SUMMARY`));let g
|
|
23
|
-
`),
|
|
24
|
-
\u274C Error:`),r.message),process.exit(1)}});function
|
|
19
|
+
`));return}switch(e.format){case"json":{let p=g.length,S=p<3?"LOW":p<8?"MEDIUM":"HIGH";console.log(JSON.stringify({target:t,isBaseTable:c,totalImpacted:p,direct:w.length,transitive:N.length,maxHopReached:h.maxHopReached,cyclesPrevented:h.cyclesPrevented,riskLevel:S,impacts:g.map(y=>({artifact:y.node.name,type:o.getDisplayGroupName(y.node.table).toLowerCase(),file:y.node.source.file,hop:y.hop,path:y.path,via:y.viaEdge.via??y.viaEdge.type,edgeType:y.viaEdge.type,state:y.node.action}))},null,2));break}case"csv":{console.log("Artifact,Type,File,Hop,Via,State,Path"),g.forEach(p=>{let S=p.viaEdge.via??p.viaEdge.type,y=p.path.join(" > ");console.log(`"${p.node.name}","${o.getDisplayGroupName(p.node.table).toLowerCase()}","${p.node.source.file}",${p.hop},"${S}","${p.node.action}","${y}"`)});break}default:{if(w.length>0){console.log(f.default.bold(`
|
|
20
|
+
\u26A1 DIRECT IMPACT`)+f.default.gray(" (Hop 1)"));let A=new Q.default({head:[f.default.cyan("Artifact"),f.default.cyan("Type"),f.default.cyan("File"),f.default.cyan("Via"),f.default.cyan("State")],wordWrap:!0,style:{head:[],border:["gray"]}});w.forEach(E=>{let I=Je(E),R=E.node.action==="DELETE"?f.default.red:f.default.green;A.push([f.default.white(E.node.name),f.default.gray(o.getDisplayGroupName(E.node.table).toLowerCase()),f.default.gray(E.node.source.file||"\u2014"),f.default.gray(I),R(E.node.action==="DELETE"?"DELETE":"ACTIVE")])}),console.log(A.toString())}if(N.length>0&&!e.directOnly){console.log(f.default.bold(`
|
|
21
|
+
\u{1F30A} TRANSITIVE IMPACT`)+f.default.gray(" (Hop 2+)"));let A=[f.default.cyan("Artifact"),f.default.cyan("Type"),f.default.cyan("File"),f.default.cyan("Hop"),f.default.cyan("State")];e.showPaths&&A.push(f.default.cyan("Path"));let E=new Q.default({head:A,wordWrap:!0,style:{head:[],border:["gray"]}});N.forEach(I=>{let R=I.node.action==="DELETE"?f.default.red:f.default.green,x=[f.default.white(I.node.name),f.default.gray(o.getDisplayGroupName(I.node.table).toLowerCase()),f.default.gray(I.node.source.file||"\u2014"),f.default.yellow(String(I.hop)),R(I.node.action==="DELETE"?"DELETE":"ACTIVE")];e.showPaths&&x.push(f.default.gray(I.path.join(" \u2192 "))),E.push(x)}),console.log(E.toString())}console.log(f.default.yellow(`
|
|
22
|
+
\u{1F4CA} IMPACT SUMMARY`));let p=g.length,S=p<3?"LOW":p<8?"MEDIUM":"HIGH",y=S==="LOW"?f.default.green:S==="MEDIUM"?f.default.yellow:f.default.red;console.log(f.default.gray("Total Impacted: ")+f.default.cyan(p)+f.default.gray(` (${w.length} direct, ${N.length} transitive)`)),console.log(f.default.gray("Max Depth Reached: ")+f.default.cyan(h.maxHopReached)),console.log(f.default.gray("Risk Level: ")+y.bold(S)),console.log(f.default.gray("\u2500".repeat(80))+`
|
|
23
|
+
`),S==="HIGH"?console.log(f.default.yellow("\u26A0\uFE0F High impact \u2014 review carefully before deploying")):S==="MEDIUM"&&console.log(f.default.yellow("\u{1F4A1} Moderate impact"));break}}}catch(r){n?console.log(JSON.stringify({error:r.message??"Unexpected error",code:"UNEXPECTED_ERROR"})):console.error(f.default.red(`
|
|
24
|
+
\u274C Error:`),r.message),process.exit(1)}});function Je(t){let e=t.viaEdge;return e.type==="schema_relationship"?`${e.via??"ref"} (ref field)`:e.via??e.type}var Ee=require("commander"),u=T(require("chalk")),ee=T(require("ora")),G=T(require("cli-table3"));var Te=T(require("crypto"));function O(t,e){let n=t.attributes[e];return typeof n=="string"?n:void 0}function Be(t,e){let n=t.attributes[e];if(typeof n=="number")return n;if(typeof n=="string"&&n.trim()!==""){let r=Number(n);return Number.isNaN(r)?void 0:r}}function He(t,e){let n=t.attributes[e];return typeof n=="boolean"?n:void 0}function Ne(t,e,n,r={}){let{depth:o=1,includeFields:s=!0,includeScripts:a=!0,includeBlastRadius:l=!0}=r,i=Math.ceil(JSON.stringify(t).length/4),c=Ue(t,e,n),m=s?qe(t,e,c):[],{scripts:v,businessRules:h}=a?ze(t,e,c,n):{scripts:[],businessRules:[]},d=ve(t,n),g=Ye(t,c),w=new Set(d.map(I=>I.table));if(o>=2)for(let I of[...d]){let R=ve(t,I.table);for(let x of R)w.has(x.table)||(w.add(x.table),d.push(x))}let N;if(l){let I=j(t,n,10),R=I.hops.filter(K=>K.hop===1&&K.node.source.file).length,x=I.hops.filter(K=>K.hop>1&&K.node.source.file).length,$=R+x,Oe=$<3?"LOW":$<8?"MEDIUM":"HIGH";N={direct:R,transitive:x,risk:Oe}}let p={target:n,fields:m,scripts:v,businessRules:h,dependents:d,references:g,blastRadius:N},S=Math.ceil(JSON.stringify(p).length/4),y=Math.max(0,i-S),A=i>0?Math.round(y/i*100):0;return{contextId:Te.default.createHash("sha256").update(JSON.stringify(p)).digest("hex").slice(0,12),target:n,fields:m,scripts:v,businessRules:h,dependents:d,references:g,blastRadius:N,tokens:{contextTokens:S,fullGraphTokens:i,tokensSaved:y,reductionPercent:A}}}function Ue(t,e,n){return t.nodes.find(r=>r.name===n&&e.getPluginName(r.table)==="TablePlugin"&&!e.isChild(r.table))??t.nodes.find(r=>r.name===n)}function qe(t,e,n){if(!n)return[];let r=t.edges.filter(s=>s.type==="composition"&&s.from===n.id).map(s=>s.to),o=[];for(let s of r){let a=t.nodes.find(c=>c.id===s);if(!a||!e.isChild(a.table))continue;let l=O(a,"element"),i=O(a,"internal_type");!l||!i||i!=="collection"&&o.push({name:l,type:i,label:O(a,"column_label")||l,referenceTable:i==="reference"?O(a,"reference"):void 0})}return o.sort((s,a)=>s.name.localeCompare(a.name))}function ze(t,e,n,r){let o=n?.id??`platform:${r}`,s=t.edges.filter(c=>c.type==="dependency"&&(c.to===o||c.targetTable===r)),a=[],l=[];for(let c of s){let m=t.nodes.find(g=>g.id===c.from);if(!m||!m.source.file)continue;let v=e.getPluginName(m.table),h=Ze(O(m,"type"),v),d={name:m.name,event:h,field:O(m,"field"),order:Be(m,"order")??100,active:He(m,"active")!==!1,file:m.source.file};v==="BusinessRulePlugin"?l.push(d):a.push(d)}let i=(c,m)=>c.order-m.order||c.name.localeCompare(m.name);return{scripts:a.sort(i),businessRules:l.sort(i)}}function ve(t,e){let n=new Set(t.nodes.filter(s=>s.name===e).map(s=>s.id));n.add(`platform:${e}`);let r=t.edges.filter(s=>s.type==="schema_relationship"&&(n.has(s.to)||s.targetTable===e)),o=new Map;for(let s of r){let a=t.nodes.find(c=>c.id===s.from);if(!a)continue;let l=a.name;if(l===e)continue;let i=s.via||O(a,"element")||"unknown";if(!o.has(l)){let c=t.edges.filter(m=>m.type==="dependency"&&m.targetTable===l).length;o.set(l,{table:l,via:i,type:"FK",artifactCount:c})}}return[...o.values()].sort((s,a)=>s.table.localeCompare(a.table))}function Ye(t,e){if(!e)return[];let n=new Set(t.edges.filter(o=>o.type==="composition"&&o.from===e.id).map(o=>o.to)),r=[];for(let o of t.edges){if(o.type!=="schema_relationship"||!n.has(o.from))continue;let s=t.nodes.find(l=>l.id===o.from);if(!s)continue;let a=o.to.startsWith("platform:")?o.to.split(":")[1]:t.nodes.find(l=>l.id===o.to)?.name??o.to;r.push({field:O(s,"element")||o.via||"unknown",targetTable:a})}return r.sort((o,s)=>o.field.localeCompare(s.field))}var Xe={onload:"onLoad",onchange:"onChange",onsubmit:"onSubmit",before:"before",after:"after",async:"async",load:"onLoad",change:"onChange",submit:"onSubmit"};function Ze(t,e){return t?Xe[t.toLowerCase()]??t:e==="BusinessRulePlugin"?"before":"action"}async function U(t){let e=()=>!0,n=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=e,process.stderr.write=e;try{return await t()}finally{process.stdout.write=n,process.stderr.write=r}}function L(t){return t.toLocaleString()}var Ie=new Ee.Command("context").description("Extract minimal AI-optimized context for a target table").argument("<target>","Target table name (e.g., account, incident)").option("--depth <n>","Dependency depth (0=target only, 1=direct, 2=two hops)","1").option("--no-fields","Exclude field details").option("--no-scripts","Exclude attached scripts").option("--no-blast","Exclude blast radius summary").option("-f, --format <type>","Output format: table, json","table").option("--show-metrics","Show per-section token breakdown and the raw JSON payload sent to an LLM").option("--no-banner","Skip banner (inherited)").action(async(t,e)=>{let n=e.format==="json",r;try{let o=process.cwd();n||(r=(0,ee.default)("Detecting ServiceNow SDK...").start());let s=await C(o);(!s.found||!s.sdkPath)&&(r&&r.fail("SDK not found"),n?console.log(JSON.stringify({error:s.error,code:"SDK_NOT_FOUND"})):console.error(u.default.red(`
|
|
25
|
+
\u274C Error:`),s.error),process.exit(1)),r&&r.succeed(`\u2705 SDK detected at ${u.default.gray(s.sdkPath)}`);let a=V(s.sdkPath),l=new a.sdkapi.Project({rootDir:o});n||(r=(0,ee.default)("Building dependency graph...").start());let i=await U(()=>M(l,a.registry));r&&(r.succeed(`Graph built: ${u.default.cyan(i.nodes.length)} nodes, ${u.default.cyan(i.edges.length)} edges`),console.log());let c=Ne(i,a.registry,t,{depth:parseInt(e.depth,10)||1,includeFields:e.fields!==!1,includeScripts:e.scripts!==!1,includeBlastRadius:e.blast!==!1});if(n){console.log(JSON.stringify(c,null,2));return}if(console.log(u.default.bold(`\u{1F9E0} CONTEXT FOR: ${u.default.white(t)}`)),console.log(u.default.gray("\u2550".repeat(52))),console.log(),console.log(`${u.default.gray("Context ID:")} ${u.default.cyan(c.contextId)}`),console.log(),c.fields.length>0){console.log(u.default.bold(`\u{1F4CB} Fields (${c.fields.length})`));let d=new G.default({head:[u.default.white("Field"),u.default.white("Type"),u.default.white("References")],style:{head:[],border:[]}});for(let g of c.fields)d.push([g.name,g.type,g.referenceTable??""]);console.log(d.toString()),console.log()}let m=[...c.scripts,...c.businessRules];if(m.length>0){console.log(u.default.bold(`\u26A1 Attached scripts (${m.length})`));let d=new G.default({head:[u.default.white("Script"),u.default.white("Event"),u.default.white("Field"),u.default.white("Order")],style:{head:[],border:[]}});for(let g of m)d.push([g.name,g.event,g.field??"",String(g.order)]);console.log(d.toString()),console.log()}if(c.dependents.length>0){console.log(u.default.bold(`\u{1F517} Dependents (${c.dependents.length} table${c.dependents.length!==1?"s":""} reference ${t})`));let d=new G.default({head:[u.default.white("Table"),u.default.white("Via field"),u.default.white("Type")],style:{head:[],border:[]}});for(let g of c.dependents)d.push([g.table,g.via,g.type]);console.log(d.toString()),console.log()}if(c.references.length>0){console.log(u.default.bold(`\u2197 References from ${t} (${c.references.length})`));let d=new G.default({head:[u.default.white("Field"),u.default.white("Points to")],style:{head:[],border:[]}});for(let g of c.references)d.push([g.field,g.targetTable]);console.log(d.toString()),console.log()}if(c.blastRadius){let{direct:d,transitive:g,risk:w}=c.blastRadius,N=w==="HIGH"?u.default.red:w==="MEDIUM"?u.default.yellow:u.default.green;console.log(`\u{1F525} Blast radius: ${d} direct, ${g} transitive \u2014 ${N(w)}`),console.log()}let{tokens:v}=c;console.log(u.default.bold("\u{1F4CA} TOKEN METRICS"));let h=new G.default({style:{head:[],border:[]}});if(h.push([u.default.gray("This context"),u.default.white(`${L(v.contextTokens)} tokens`)],[u.default.gray("Full graph"),u.default.white(`${L(v.fullGraphTokens)} tokens`)],[u.default.gray("Saved"),u.default.green(`${L(v.tokensSaved)} tokens`)],[u.default.gray("Reduction"),u.default.green(`${v.reductionPercent}%`)]),console.log(h.toString()),e.showMetrics){let d={target:c.target,fields:c.fields,scripts:c.scripts,businessRules:c.businessRules,dependents:c.dependents,references:c.references,blastRadius:c.blastRadius},g=[{label:"target",value:c.target},{label:"fields",value:c.fields},{label:"scripts",value:c.scripts},{label:"businessRules",value:c.businessRules},{label:"dependents",value:c.dependents},{label:"references",value:c.references},{label:"blastRadius",value:c.blastRadius}];console.log(),console.log(u.default.bold("\u{1F52C} METRICS BREAKDOWN (chars \xF7 4 \u2248 tokens)")),console.log(u.default.gray("\u2550".repeat(52)));let w=new G.default({head:[u.default.white("Section"),u.default.white("Chars"),u.default.white("~Tokens"),u.default.white("% of context")],style:{head:[],border:[]}}),N=JSON.stringify(d).length;for(let{label:p,value:S}of g){let y=JSON.stringify(S).length,A=Math.ceil(y/4),E=N>0?Math.round(y/N*100):0;w.push([u.default.gray(p),L(y),L(A),`${E}%`])}w.push([u.default.white("TOTAL"),u.default.white(L(N)),u.default.white(L(v.contextTokens)),u.default.white("100%")]),console.log(w.toString()),console.log(),console.log(u.default.bold("\u{1F4E6} RAW PAYLOAD (what an LLM receives)")),console.log(u.default.gray("\u2550".repeat(52))),console.log(u.default.gray("Formula: Math.ceil(JSON.stringify(payload).length / 4)")),console.log(u.default.gray(` Math.ceil(${L(N)} / 4) = ${L(v.contextTokens)} tokens`)),console.log(),console.log(JSON.stringify(d,null,2))}}catch(o){let s=o instanceof Error?o.message:"Unexpected error";n?console.log(JSON.stringify({error:s,code:"CONTEXT_FAILED"})):console.error(u.default.red(`
|
|
26
|
+
\u274C ${s}`)),process.exit(1)}});var De=require("commander"),k=T(require("chalk")),te=T(require("ora"));function q(t,e){let n=t.attributes[e];return typeof n=="string"?n:void 0}function Qe(t,e){let n=t.attributes[e];if(typeof n=="number")return n;if(typeof n=="string"&&n.trim()!==""){let r=Number(n);return Number.isNaN(r)?void 0:r}}function et(t,e){let n=t.length,r=e.length,o=Array.from({length:n+1},(s,a)=>Array.from({length:r+1},(l,i)=>a===0?i:i===0?a:0));for(let s=1;s<=n;s++)for(let a=1;a<=r;a++)o[s][a]=t[s-1]===e[a-1]?o[s-1][a-1]:1+Math.min(o[s-1][a-1],o[s-1][a],o[s][a-1]);return o[n][r]}function tt(t,e){let n,r=1/0;for(let o of e){let s=et(t.toLowerCase(),o.toLowerCase());s<r&&(r=s,n=o)}return n&&r<=Math.max(2,Math.floor(t.length*.4))?n:void 0}function nt(t,e){let n=[],r=new Map;for(let o of t.nodes){if(!e.isChild(o.table))continue;let s=q(o,"element");if(!s)continue;let a=t.nodes.find(i=>t.edges.some(c=>c.type==="composition"&&c.from===i.id&&c.to===o.id))?.name;if(!a)continue;let l=r.get(a)??[];l.push(s),r.set(a,l)}for(let o of t.edges){if(o.type!=="dependency")continue;let s=t.nodes.find(m=>m.id===o.from);if(!s?.source.file)continue;let a=q(s,"field");if(!a)continue;let l=o.targetTable??t.nodes.find(m=>m.id===o.to)?.name;if(!l)continue;let i=r.get(l);if(!i||i.includes(a))continue;let c=tt(a,i);n.push({severity:"error",code:"FG-001",message:`"${s.name}" references field "${a}" on table "${l}" but no such field exists`,artifact:s.name,table:l,file:s.source.file,suggestion:c?`Did you mean "${c}"?`:void 0})}return n}function ot(t){let e=[],n=new Map;for(let r of t.edges){if(r.type!=="dependency")continue;let o=t.nodes.find(c=>c.id===r.from);if(!o?.source.file)continue;let s=r.targetTable??t.nodes.find(c=>c.id===r.to)?.name??"unknown",a=q(o,"type")??"unknown",l=`${s}::${a}`,i=n.get(l)??[];i.push(o),n.set(l,i)}for(let[r,o]of n){let s=new Map;for(let a of o){let l=Qe(a,"order")??100,i=s.get(l)??[];i.push(a),s.set(l,i)}for(let[a,l]of s){if(l.length<2)continue;let[i,c]=r.split("::"),m=l.map(v=>v.name).join(" and ");e.push({severity:"warning",code:"FG-002",message:`${m} (order: ${a}) both fire "${c}" on "${i}" \u2014 nondeterministic execution order`,table:i,file:l[0].source.file})}}return e}function rt(t){let e=[],n=new Set(t.nodes.map(r=>r.id));for(let r of t.edges){if(r.type!=="schema_relationship"||r.to.startsWith("platform:")||n.has(r.to))continue;let o=t.nodes.find(l=>l.id===r.from);if(!o)continue;let s=r.to,a=q(o,"element");e.push({severity:"warning",code:"FG-003",message:`Field "${a??r.via??"unknown"}" on "${o.name}" references "${s}" but this table is not in the local project or known platform tables`,artifact:o.name,file:o.source.file})}return e}function st(t){let e=[],n=new Map;for(let r of t.nodes){let o=`${r.table}::${r.name}`,s=n.get(o)??[];s.push(r),n.set(o,s)}for(let[r,o]of n){if(o.length<2)continue;let[s,a]=r.split("::");e.push({severity:"warning",code:"FG-004",message:`Two artifacts named "${a}" exist on "${s}" \u2014 the last defined wins, the other is shadowed`,artifact:a,table:s,file:o[0].source.file})}return e}function it(t,e){let n=[],r=t.nodes.filter(o=>o.action==="DELETE");for(let o of r){let a=j(t,o.name,5).hops.filter(i=>i.node.action!=="DELETE"&&i.node.source.file);if(a.length===0)continue;let l=e.getDisplayGroupName(o.table).toLowerCase();n.push({severity:"error",code:"FG-005",message:`"${o.name}" (${l}) is marked DELETE but ${a.length} active artifact(s) still reference it \u2014 these will break after deployment`,artifact:o.name,table:o.table,file:o.source.file,suggestion:`Review: ${a.slice(0,3).map(i=>i.node.name).join(", ")}${a.length>3?" ...":""}`})}return n}function ke(t,e){let n=[...nt(t,e),...ot(t),...rt(t),...st(t),...it(t,e)],r={error:0,warning:1,info:2};n.sort((l,i)=>r[l.severity]-r[i.severity]);let o=n.filter(l=>l.severity==="error"),s=n.filter(l=>l.severity==="warning"),a=n.filter(l=>l.severity==="info");return{valid:o.length===0,issueCount:n.length,errors:o,warnings:s,info:a}}var at={error:k.default.red(" ERROR"),warning:k.default.yellow("WARNING"),info:k.default.cyan(" INFO")};function ct(t){let e=[`${at[t.severity]} [${t.code}] ${t.message}`];return t.file&&e.push(k.default.gray(` File: ${t.file}`)),t.suggestion&&e.push(k.default.cyan(` Suggestion: ${t.suggestion}`)),e.join(`
|
|
27
|
+
`)}function lt(t){return t==="error"||t==="warning"||t==="info"}function dt(t,e){switch(e){case"error":return t==="error";case"warning":return t==="error"||t==="warning";case"info":return!0;default:{let n=e;throw new Error(`Unhandled severity: ${JSON.stringify(n)}`)}}}var Re=new De.Command("validate").description("Run structural validation on the current project").option("-f, --format <type>","Output format: table, json","table").option("--severity <level>","Minimum severity to show: error, warning, info","info").option("--no-banner","Skip banner (inherited)").action(async t=>{let e=t.format==="json",n;try{let r=process.cwd();e||(n=(0,te.default)("Detecting ServiceNow SDK...").start());let o=await C(r);(!o.found||!o.sdkPath)&&(n&&n.fail("SDK not found"),e?console.log(JSON.stringify({error:o.error,code:"SDK_NOT_FOUND"})):console.error(k.default.red(`
|
|
28
|
+
\u274C Error:`),o.error),process.exit(1)),n&&n.succeed(`\u2705 SDK detected at ${k.default.gray(o.sdkPath)}`);let s=V(o.sdkPath),a=new s.sdkapi.Project({rootDir:r});e||(n=(0,te.default)("Building dependency graph...").start());let l=await U(()=>M(a,s.registry));n&&(n.succeed(`Graph built: ${k.default.cyan(l.nodes.length)} nodes, ${k.default.cyan(l.edges.length)} edges`),console.log());let i=ke(l,s.registry);if(e){console.log(JSON.stringify(i,null,2)),process.exit(i.valid?0:1);return}console.log(k.default.bold("\u{1F50D} STRUCTURAL VALIDATION")),console.log(k.default.gray("\u2550".repeat(44))),console.log();let c=lt(t.severity)?t.severity:"info",m=d=>dt(d.severity,c),v=i.errors.filter(m).concat(i.warnings.filter(m)).concat(i.info.filter(m));if(v.length===0)console.log(k.default.green(" \u2705 No issues found"));else for(let d of v)console.log(ct(d)),console.log();console.log(k.default.gray("\u2550".repeat(44)));let h=[i.errors.length?k.default.red(`${i.errors.length} error${i.errors.length!==1?"s":""}`):null,i.warnings.length?k.default.yellow(`${i.warnings.length} warning${i.warnings.length!==1?"s":""}`):null,i.info.length?k.default.cyan(`${i.info.length} info`):null].filter(Boolean);console.log(h.length?h.join(", "):k.default.green("0 issues")),process.exit(i.valid?0:1)}catch(r){let o=r instanceof Error?r.message:"Unexpected error";e?console.log(JSON.stringify({error:o,code:"VALIDATION_FAILED"})):console.error(k.default.red(`
|
|
29
|
+
\u274C ${o}`)),process.exit(1)}});var z=T(require("chalk"));function Ae(){console.log(z.default.cyan(`
|
|
25
30
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
|
|
26
31
|
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
27
32
|
\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551
|
|
28
33
|
\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551
|
|
29
34
|
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
30
35
|
\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D
|
|
31
|
-
`)),console.log(
|
|
32
|
-
`))}var
|
|
36
|
+
`)),console.log(z.default.gray(" Dependency visibility and impact analysis for ServiceNow Fluent SDK projects")),console.log(z.default.gray(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
37
|
+
`))}var ft=Le.default.join(__dirname,"../../package.json"),gt=Ce.default.readFileSync(ft,"utf-8"),ne=JSON.parse(gt),P=new xe.Command,pt=(()=>{let t=process.argv;for(let e=0;e<t.length;e++){let n=t[e];if(n==="--format"||n==="-f")return t[e+1]==="json";if(n==="--format=json"||n==="-f=json")return!0}return!1})(),ut=!process.argv.includes("--no-banner")&&!process.argv.includes("--quiet")&&!pt;ut&&Ae();P.name(ne.name).description(ne.description).version(ne.version).option("--no-banner","Suppress ASCII banner");P.addCommand(be);P.addCommand(Se);P.addCommand(Ie);P.addCommand(Re);process.argv.length===2&&P.help();P.parse(process.argv);
|
package/dist/index.d.ts
CHANGED
|
@@ -105,6 +105,9 @@ interface TraversalResult {
|
|
|
105
105
|
* - edge.to === currentNodeId (exact physical node)
|
|
106
106
|
* - edge.targetTable === currentNodeName (canonical name — reliable
|
|
107
107
|
* for platform nodes and catches mis-targeted edges)
|
|
108
|
+
* This lookup is served by an adjacency index (buildIncomingEdgeIndex)
|
|
109
|
+
* built once up front, rather than re-scanning all of graph.edges on
|
|
110
|
+
* every BFS step.
|
|
108
111
|
*
|
|
109
112
|
* 4. ALL edge types are traversed. Display filtering is the caller's job.
|
|
110
113
|
*
|
|
@@ -114,4 +117,120 @@ interface TraversalResult {
|
|
|
114
117
|
*/
|
|
115
118
|
declare function traverseBlastRadius(graph: LineageGraph, targetTableName: string, maxDepth: number): TraversalResult;
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
interface ImpactedArtifact {
|
|
121
|
+
name: string;
|
|
122
|
+
table: string;
|
|
123
|
+
type: string;
|
|
124
|
+
file: string;
|
|
125
|
+
reason: string;
|
|
126
|
+
state: string;
|
|
127
|
+
hop: number;
|
|
128
|
+
}
|
|
129
|
+
interface BlastRadiusResult {
|
|
130
|
+
target: string;
|
|
131
|
+
isBaseTable: boolean;
|
|
132
|
+
direct: ImpactedArtifact[];
|
|
133
|
+
transitive: ImpactedArtifact[];
|
|
134
|
+
totalImpacted: number;
|
|
135
|
+
maxDepth: number;
|
|
136
|
+
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH';
|
|
137
|
+
}
|
|
138
|
+
interface ContextOptions {
|
|
139
|
+
depth?: number;
|
|
140
|
+
includeFields?: boolean;
|
|
141
|
+
includeScripts?: boolean;
|
|
142
|
+
includeBlastRadius?: boolean;
|
|
143
|
+
}
|
|
144
|
+
interface FieldInfo {
|
|
145
|
+
name: string;
|
|
146
|
+
type: string;
|
|
147
|
+
label: string;
|
|
148
|
+
referenceTable?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The known Fluent script events, plus an escape hatch for whatever the SDK
|
|
152
|
+
* hands back that isn't one of them (the "LiteralUnion" pattern: keeps
|
|
153
|
+
* autocomplete/type-checking for the known cases without discarding an
|
|
154
|
+
* unrecognized runtime value by coercing it to one of the literals).
|
|
155
|
+
*/
|
|
156
|
+
type KnownScriptEventType = 'onLoad' | 'onChange' | 'onSubmit' | 'before' | 'after' | 'async' | 'action';
|
|
157
|
+
type ScriptEventType = KnownScriptEventType | (string & {});
|
|
158
|
+
interface ScriptInfo {
|
|
159
|
+
name: string;
|
|
160
|
+
event: ScriptEventType;
|
|
161
|
+
field?: string;
|
|
162
|
+
order: number;
|
|
163
|
+
active: boolean;
|
|
164
|
+
file: string;
|
|
165
|
+
}
|
|
166
|
+
interface DependentInfo {
|
|
167
|
+
table: string;
|
|
168
|
+
via: string;
|
|
169
|
+
type: string;
|
|
170
|
+
artifactCount: number;
|
|
171
|
+
}
|
|
172
|
+
interface ReferenceInfo {
|
|
173
|
+
field: string;
|
|
174
|
+
targetTable: string;
|
|
175
|
+
}
|
|
176
|
+
interface ContextResult {
|
|
177
|
+
/** Deterministic sha256 fingerprint (12 hex chars) of this context snapshot.
|
|
178
|
+
* Consumers log this alongside generated code to create a provenance trail. */
|
|
179
|
+
contextId: string;
|
|
180
|
+
target: string;
|
|
181
|
+
fields: FieldInfo[];
|
|
182
|
+
scripts: ScriptInfo[];
|
|
183
|
+
businessRules: ScriptInfo[];
|
|
184
|
+
dependents: DependentInfo[];
|
|
185
|
+
references: ReferenceInfo[];
|
|
186
|
+
blastRadius?: {
|
|
187
|
+
direct: number;
|
|
188
|
+
transitive: number;
|
|
189
|
+
risk: string;
|
|
190
|
+
};
|
|
191
|
+
tokens: {
|
|
192
|
+
contextTokens: number;
|
|
193
|
+
fullGraphTokens: number;
|
|
194
|
+
tokensSaved: number;
|
|
195
|
+
reductionPercent: number;
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
type ValidationSeverity = 'error' | 'warning' | 'info';
|
|
199
|
+
/** Each code maps to exactly one structural check in analysis/validate.ts. */
|
|
200
|
+
type ValidationCode = 'FG-001' | 'FG-002' | 'FG-003' | 'FG-004' | 'FG-005';
|
|
201
|
+
interface ValidationIssue {
|
|
202
|
+
severity: ValidationSeverity;
|
|
203
|
+
code: ValidationCode;
|
|
204
|
+
message: string;
|
|
205
|
+
artifact?: string;
|
|
206
|
+
table?: string;
|
|
207
|
+
file?: string;
|
|
208
|
+
suggestion?: string;
|
|
209
|
+
}
|
|
210
|
+
interface ValidationResult {
|
|
211
|
+
valid: boolean;
|
|
212
|
+
issueCount: number;
|
|
213
|
+
errors: ValidationIssue[];
|
|
214
|
+
warnings: ValidationIssue[];
|
|
215
|
+
info: ValidationIssue[];
|
|
216
|
+
}
|
|
217
|
+
interface ProjectSummary {
|
|
218
|
+
totalArtifacts: number;
|
|
219
|
+
totalRelationships: number;
|
|
220
|
+
artifactsByPlugin: Record<string, number>;
|
|
221
|
+
platformDependencies: number;
|
|
222
|
+
internalReferences: number;
|
|
223
|
+
complexity: number;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Extract a minimal subgraph optimised for LLM consumption.
|
|
228
|
+
*
|
|
229
|
+
* No sys_ids, no SDK internals — just the structural facts an agent needs
|
|
230
|
+
* to understand what a table looks like and what will break if it changes.
|
|
231
|
+
*/
|
|
232
|
+
declare function selectContext(graph: LineageGraph, registry: ArtifactTypeRegistry, targetTableName: string, options?: ContextOptions): ContextResult;
|
|
233
|
+
|
|
234
|
+
declare function validateGraph(graph: LineageGraph, registry: ArtifactTypeRegistry): ValidationResult;
|
|
235
|
+
|
|
236
|
+
export { type ArtifactEdge, type ArtifactNode, type BlastRadiusResult, type ContextOptions, type ContextResult, type DependentInfo, type FieldInfo, type GraphMetadata, type ImpactedArtifact, type LineageGraph, type ProjectSummary, type ReferenceInfo, type ScriptEventType, type ScriptInfo, type TraversalNode, type TraversalResult, type ValidationCode, type ValidationIssue, type ValidationResult, type ValidationSeverity, extractLineageGraph, selectContext, traverseBlastRadius, validateGraph };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var k=Object.create;var A=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var F=Object.getOwnPropertyNames;var M=Object.getPrototypeOf,L=Object.prototype.hasOwnProperty;var D=(t,e)=>{for(var n in e)A(t,n,{get:e[n],enumerable:!0})},x=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of F(e))!L.call(t,r)&&r!==n&&A(t,r,{get:()=>e[r],enumerable:!(o=G(e,r))||o.enumerable});return t};var O=(t,e,n)=>(n=t!=null?k(M(t)):{},x(e||!t||!t.__esModule?A(n,"default",{value:t,enumerable:!0}):n,t)),q=t=>x(A({},"__esModule",{value:!0}),t);var z={};D(z,{extractLineageGraph:()=>$,traverseBlastRadius:()=>_});module.exports=q(z);var R=O(require("path"));function C(t){let e={};for(let[n,o]of Object.entries(t))if(o)try{let r=typeof o.getValue=="function"?o.getValue():o;(typeof r!="object"||r===null||Array.isArray(r))&&(e[n]=r)}catch{continue}return e}function V(t,e){let n=[];for(let o of t.query()){let r=o.setProperties||{},i=o.getId()?.getValue()?.toString()||"",c=o.getTable()?.toString()||"",f=r.name?.getValue?.()?.toString()||r.label?.getValue?.()?.toString()||r.sys_name?.getValue?.()?.toString()||i,m=o.getOriginalFilePath()?.toString()||"",y=m?R.default.relative(e,m):"";n.push({id:i,table:c,action:o.getAction(),name:f,attributes:C(r),source:{file:y,type:o.getOriginalSource()?.getKindName?.()||"SourceFile"}})}return n}function E(t,e,n){let o=[];for(let r of t.query()){let i=r.getId()?.getValue()?.toString(),c=r.setProperties||{},f=r.getTable()?.toString(),m=c.internal_type?.getValue?.()?.toString(),y=c.reference?.getValue?.()?.toString(),b=c.element?.getValue?.()?.toString();if(m==="reference"&&y){let g=e.find(s=>s.name===y);o.push({from:i,to:g?.id||`platform:${y}`,via:b||"reference",type:"schema_relationship"})}let u=c.table?.getValue?.()?.toString()||c.collection?.getValue?.()?.toString();if(u){let g=e.find(s=>s.name===u);o.push({from:i,to:g?.id||`platform:${u}`,via:c.table?"table":"collection",type:"dependency",targetTable:u})}if(n.getPluginName(f)==="AclPlugin"){let g=c.name?.getValue?.()?.toString();if(g&&g.includes(".")){let[s]=g.split("."),d=e.find(a=>a.name===s);o.push({from:i,to:d?.id||`platform:${s}`,via:"name",type:"dependency",targetTable:s})}}for(let[g,s]of Object.entries(c))if(Array.isArray(s))s.forEach(d=>{if(d&&d.constructor?.name==="RecordId"){let a=d.getValue?.()?.toString(),p=d.getTable?.()?.toString();a&&n.getPluginName(p)!=="TablePlugin"&&o.push({from:i,to:a,via:g,type:"dependency",targetTable:p})}});else if(s&&s.constructor?.name==="RecordId"){let d=s.getValue?.()?.toString(),a=s.getTable?.()?.toString();d&&n.getPluginName(a)!=="TablePlugin"&&o.push({from:i,to:d,via:g,type:"dependency",targetTable:a})}else if(s&&s.constructor?.name==="Record"){let d=s.getId?.()?.getValue?.()?.toString();if(d){let a=e.find(p=>p.id===d);a&&n.getPluginName(a.table)!=="TablePlugin"&&o.push({from:i,to:d,via:g,type:"dependency",targetTable:a.table})}}}return o}function T(t){let e=[];for(let n of t.query()){let o=n.getId()?.getValue()?.toString(),r=n.getRelated?.()||[];for(let i of r){let c=i.getId()?.getValue()?.toString();c&&o&&e.push({from:o,to:c,type:"composition"})}}return e}function P(t,e,n,o){let r=t.getConfig()||{},i=t.getPackage()||{};return{projectRoot:t.getRootDir()||process.cwd(),scopeId:r.scopeId||"unknown",scopeName:r.scope||"Global",recordCount:e,referenceCount:n,compositionCount:o,generatedAt:new Date().toISOString(),sdkVersion:i.dependencies?.["@servicenow/sdk"]||i.devDependencies?.["@servicenow/sdk"]||"unknown"}}async function $(t,e){let n=await t.getRecords(),o=t.getRootDir(),r=V(n,o),i=E(n,r,e),c=T(n),f=[...i,...c];return{metadata:P(t,n.query().length,i.length,c.length),nodes:r,edges:f}}function H(t){let e=new Map;for(let n of t.nodes){let o=e.get(n.name)??[];o.push(n.id),e.set(n.name,o)}return e}function _(t,e,n){let o=Math.min(n,25),r=H(t),i=t.nodes.filter(a=>a.name===e),c=i[0]??null,f=new Set,m=`platform:${e}`;f.add(m);for(let a of i)f.add(a.id);let b=[...new Set([m,...i.map(a=>a.id)])].map(a=>({nodeId:a,nodeName:e,hop:0,path:[e]})),u=[],g=new Set([e]),s=0,d=0;for(;b.length>0;){let a=[];for(let p of b){if(p.hop>=o)continue;let w=t.edges.filter(N=>N.to===p.nodeId||p.nodeName.length>0&&N.targetTable===p.nodeName);for(let N of w){let l=t.nodes.find(h=>h.id===N.from);if(!l)continue;if(f.has(l.id)){d++;continue}f.add(l.id);let I=r.get(l.name)??[];for(let h of I)f.has(h)||f.add(h);let S=p.hop+1,v=[...p.path,l.name];g.has(l.name)||(g.add(l.name),u.push({node:l,hop:S,path:v,viaEdge:N}));for(let h of I)a.push({nodeId:h,nodeName:l.name,hop:S,path:v});S>s&&(s=S)}}b=a}return u.sort((a,p)=>a.hop-p.hop||a.node.name.localeCompare(p.node.name)),{target:c,targetName:e,hops:u,maxHopReached:s,cyclesPrevented:d}}0&&(module.exports={extractLineageGraph,traverseBlastRadius});
|
|
1
|
+
"use strict";var H=Object.create;var V=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,J=Object.prototype.hasOwnProperty;var U=(t,i)=>{for(var n in i)V(t,n,{get:i[n],enumerable:!0})},L=(t,i,n,o)=>{if(i&&typeof i=="object"||typeof i=="function")for(let e of q(i))!J.call(t,e)&&e!==n&&V(t,e,{get:()=>i[e],enumerable:!(o=W(i,e))||o.enumerable});return t};var G=(t,i,n)=>(n=t!=null?H(j(t)):{},L(i||!t||!t.__esModule?V(n,"default",{value:t,enumerable:!0}):n,t)),z=t=>L(V({},"__esModule",{value:!0}),t);var be={};U(be,{extractLineageGraph:()=>Q,selectContext:()=>B,traverseBlastRadius:()=>w,validateGraph:()=>K});module.exports=z(be);var F=G(require("path"));function Y(t){let i={};for(let[n,o]of Object.entries(t))if(o)try{let e=typeof o.getValue=="function"?o.getValue():o;(typeof e!="object"||e===null||Array.isArray(e))&&(i[n]=e)}catch{continue}return i}function C(t,i){let n=[];for(let o of t.query()){let e=o.setProperties||{},r=o.getId()?.getValue()?.toString()||"",s=o.getTable()?.toString()||"",a=e.name?.getValue?.()?.toString()||e.label?.getValue?.()?.toString()||e.sys_name?.getValue?.()?.toString()||r,c=o.getOriginalFilePath()?.toString()||"",d=c?F.default.relative(i,c):"";n.push({id:r,table:s,action:o.getAction(),name:a,attributes:Y(e),source:{file:d,type:o.getOriginalSource()?.getKindName?.()||"SourceFile"}})}return n}function M(t,i,n){let o=[];for(let e of t.query()){let r=e.getId()?.getValue()?.toString(),s=e.setProperties||{},a=e.getTable()?.toString(),c=s.internal_type?.getValue?.()?.toString(),d=s.reference?.getValue?.()?.toString(),l=s.element?.getValue?.()?.toString();if(c==="reference"&&d){let g=i.find(f=>f.name===d);o.push({from:r,to:g?.id||`platform:${d}`,via:l||"reference",type:"schema_relationship"})}let y=s.table?.getValue?.()?.toString()||s.collection?.getValue?.()?.toString();if(y){let g=i.find(f=>f.name===y);o.push({from:r,to:g?.id||`platform:${y}`,via:s.table?"table":"collection",type:"dependency",targetTable:y})}if(n.getPluginName(a)==="AclPlugin"){let g=s.name?.getValue?.()?.toString();if(g&&g.includes(".")){let[f]=g.split("."),u=i.find(m=>m.name===f);o.push({from:r,to:u?.id||`platform:${f}`,via:"name",type:"dependency",targetTable:f})}}for(let[g,f]of Object.entries(s))if(Array.isArray(f))f.forEach(u=>{if(u&&u.constructor?.name==="RecordId"){let m=u.getValue?.()?.toString(),b=u.getTable?.()?.toString();m&&n.getPluginName(b)!=="TablePlugin"&&o.push({from:r,to:m,via:g,type:"dependency",targetTable:b})}});else if(f&&f.constructor?.name==="RecordId"){let u=f.getValue?.()?.toString(),m=f.getTable?.()?.toString();u&&n.getPluginName(m)!=="TablePlugin"&&o.push({from:r,to:u,via:g,type:"dependency",targetTable:m})}else if(f&&f.constructor?.name==="Record"){let u=f.getId?.()?.getValue?.()?.toString();if(u){let m=i.find(b=>b.id===u);m&&n.getPluginName(m.table)!=="TablePlugin"&&o.push({from:r,to:u,via:g,type:"dependency",targetTable:m.table})}}}return o}function $(t){let i=[];for(let n of t.query()){let o=n.getId()?.getValue()?.toString(),e=n.getRelated?.()||[];for(let r of e){let s=r.getId()?.getValue()?.toString();s&&o&&i.push({from:o,to:s,type:"composition"})}}return i}function P(t,i,n,o){let e=t.getConfig()||{},r=t.getPackage()||{};return{projectRoot:t.getRootDir()||process.cwd(),scopeId:e.scopeId||"unknown",scopeName:e.scope||"Global",recordCount:i,referenceCount:n,compositionCount:o,generatedAt:new Date().toISOString(),sdkVersion:r.dependencies?.["@servicenow/sdk"]||r.devDependencies?.["@servicenow/sdk"]||"unknown"}}async function Q(t,i){let n=await t.getRecords(),o=t.getRootDir(),e=C(n,o),r=M(n,e,i),s=$(n),a=[...r,...s];return{metadata:P(t,n.query().length,r.length,s.length),nodes:e,edges:a}}function X(t){let i=new Map;for(let n of t.nodes){let o=i.get(n.name)??[];o.push(n.id),i.set(n.name,o)}return i}function Z(t){let i=new Map,n=new Map,o=new Map;return t.edges.forEach((e,r)=>{o.set(e,r);let s=i.get(e.to)??[];if(s.push(e),i.set(e.to,s),e.targetTable){let a=n.get(e.targetTable)??[];a.push(e),n.set(e.targetTable,a)}}),{byNodeId:i,byTargetTable:n,originalIndexByEdge:o}}function ee(t,i,n){let{byNodeId:o,byTargetTable:e,originalIndexByEdge:r}=t,s=new Set(o.get(i)??[]);if(n.length>0)for(let a of e.get(n)??[])s.add(a);return[...s].sort((a,c)=>r.get(a)-r.get(c))}function w(t,i,n){let o=Math.min(n,25),e=X(t),r=Z(t),s=new Map(t.nodes.map(p=>[p.id,p])),a=t.nodes.filter(p=>p.name===i),c=a[0]??null,d=new Set,l=`platform:${i}`;d.add(l);for(let p of a)d.add(p.id);let g=[...new Set([l,...a.map(p=>p.id)])].map(p=>({nodeId:p,nodeName:i,hop:0,path:[i]})),f=[],u=new Set([i]),m=0,b=0;for(;g.length>0;){let p=[];for(let h of g){if(h.hop>=o)continue;let R=ee(r,h.nodeId,h.nodeName);for(let x of R){let I=s.get(x.from);if(!I)continue;if(d.has(I.id)){b++;continue}d.add(I.id);let N=e.get(I.name)??[];for(let E of N)d.has(E)||d.add(E);let v=h.hop+1,A=[...h.path,I.name];u.has(I.name)||(u.add(I.name),f.push({node:I,hop:v,path:A,viaEdge:x}));for(let E of N)p.push({nodeId:E,nodeName:I.name,hop:v,path:A});v>m&&(m=v)}}g=p}return f.sort((p,h)=>p.hop-h.hop||p.node.name.localeCompare(h.node.name)),{target:c,targetName:i,hops:f,maxHopReached:m,cyclesPrevented:b}}var O=G(require("crypto"));function T(t,i){let n=t.attributes[i];return typeof n=="string"?n:void 0}function te(t,i){let n=t.attributes[i];if(typeof n=="number")return n;if(typeof n=="string"&&n.trim()!==""){let o=Number(n);return Number.isNaN(o)?void 0:o}}function ne(t,i){let n=t.attributes[i];return typeof n=="boolean"?n:void 0}function B(t,i,n,o={}){let{depth:e=1,includeFields:r=!0,includeScripts:s=!0,includeBlastRadius:a=!0}=o,c=Math.ceil(JSON.stringify(t).length/4),d=oe(t,i,n),l=r?ie(t,i,d):[],{scripts:y,businessRules:g}=s?re(t,i,d,n):{scripts:[],businessRules:[]},f=D(t,n),u=se(t,d),m=new Set(f.map(N=>N.table));if(e>=2)for(let N of[...f]){let v=D(t,N.table);for(let A of v)m.has(A.table)||(m.add(A.table),f.push(A))}let b;if(a){let N=w(t,n,10),v=N.hops.filter(S=>S.hop===1&&S.node.source.file).length,A=N.hops.filter(S=>S.hop>1&&S.node.source.file).length,E=v+A,_=E<3?"LOW":E<8?"MEDIUM":"HIGH";b={direct:v,transitive:A,risk:_}}let p={target:n,fields:l,scripts:y,businessRules:g,dependents:f,references:u,blastRadius:b},h=Math.ceil(JSON.stringify(p).length/4),R=Math.max(0,c-h),x=c>0?Math.round(R/c*100):0;return{contextId:O.default.createHash("sha256").update(JSON.stringify(p)).digest("hex").slice(0,12),target:n,fields:l,scripts:y,businessRules:g,dependents:f,references:u,blastRadius:b,tokens:{contextTokens:h,fullGraphTokens:c,tokensSaved:R,reductionPercent:x}}}function oe(t,i,n){return t.nodes.find(o=>o.name===n&&i.getPluginName(o.table)==="TablePlugin"&&!i.isChild(o.table))??t.nodes.find(o=>o.name===n)}function ie(t,i,n){if(!n)return[];let o=t.edges.filter(r=>r.type==="composition"&&r.from===n.id).map(r=>r.to),e=[];for(let r of o){let s=t.nodes.find(d=>d.id===r);if(!s||!i.isChild(s.table))continue;let a=T(s,"element"),c=T(s,"internal_type");!a||!c||c!=="collection"&&e.push({name:a,type:c,label:T(s,"column_label")||a,referenceTable:c==="reference"?T(s,"reference"):void 0})}return e.sort((r,s)=>r.name.localeCompare(s.name))}function re(t,i,n,o){let e=n?.id??`platform:${o}`,r=t.edges.filter(d=>d.type==="dependency"&&(d.to===e||d.targetTable===o)),s=[],a=[];for(let d of r){let l=t.nodes.find(u=>u.id===d.from);if(!l||!l.source.file)continue;let y=i.getPluginName(l.table),g=ce(T(l,"type"),y),f={name:l.name,event:g,field:T(l,"field"),order:te(l,"order")??100,active:ne(l,"active")!==!1,file:l.source.file};y==="BusinessRulePlugin"?a.push(f):s.push(f)}let c=(d,l)=>d.order-l.order||d.name.localeCompare(l.name);return{scripts:s.sort(c),businessRules:a.sort(c)}}function D(t,i){let n=new Set(t.nodes.filter(r=>r.name===i).map(r=>r.id));n.add(`platform:${i}`);let o=t.edges.filter(r=>r.type==="schema_relationship"&&(n.has(r.to)||r.targetTable===i)),e=new Map;for(let r of o){let s=t.nodes.find(d=>d.id===r.from);if(!s)continue;let a=s.name;if(a===i)continue;let c=r.via||T(s,"element")||"unknown";if(!e.has(a)){let d=t.edges.filter(l=>l.type==="dependency"&&l.targetTable===a).length;e.set(a,{table:a,via:c,type:"FK",artifactCount:d})}}return[...e.values()].sort((r,s)=>r.table.localeCompare(s.table))}function se(t,i){if(!i)return[];let n=new Set(t.edges.filter(e=>e.type==="composition"&&e.from===i.id).map(e=>e.to)),o=[];for(let e of t.edges){if(e.type!=="schema_relationship"||!n.has(e.from))continue;let r=t.nodes.find(a=>a.id===e.from);if(!r)continue;let s=e.to.startsWith("platform:")?e.to.split(":")[1]:t.nodes.find(a=>a.id===e.to)?.name??e.to;o.push({field:T(r,"element")||e.via||"unknown",targetTable:s})}return o.sort((e,r)=>e.field.localeCompare(r.field))}var ae={onload:"onLoad",onchange:"onChange",onsubmit:"onSubmit",before:"before",after:"after",async:"async",load:"onLoad",change:"onChange",submit:"onSubmit"};function ce(t,i){return t?ae[t.toLowerCase()]??t:i==="BusinessRulePlugin"?"before":"action"}function k(t,i){let n=t.attributes[i];return typeof n=="string"?n:void 0}function de(t,i){let n=t.attributes[i];if(typeof n=="number")return n;if(typeof n=="string"&&n.trim()!==""){let o=Number(n);return Number.isNaN(o)?void 0:o}}function fe(t,i){let n=t.length,o=i.length,e=Array.from({length:n+1},(r,s)=>Array.from({length:o+1},(a,c)=>s===0?c:c===0?s:0));for(let r=1;r<=n;r++)for(let s=1;s<=o;s++)e[r][s]=t[r-1]===i[s-1]?e[r-1][s-1]:1+Math.min(e[r-1][s-1],e[r-1][s],e[r][s-1]);return e[n][o]}function le(t,i){let n,o=1/0;for(let e of i){let r=fe(t.toLowerCase(),e.toLowerCase());r<o&&(o=r,n=e)}return n&&o<=Math.max(2,Math.floor(t.length*.4))?n:void 0}function ue(t,i){let n=[],o=new Map;for(let e of t.nodes){if(!i.isChild(e.table))continue;let r=k(e,"element");if(!r)continue;let s=t.nodes.find(c=>t.edges.some(d=>d.type==="composition"&&d.from===c.id&&d.to===e.id))?.name;if(!s)continue;let a=o.get(s)??[];a.push(r),o.set(s,a)}for(let e of t.edges){if(e.type!=="dependency")continue;let r=t.nodes.find(l=>l.id===e.from);if(!r?.source.file)continue;let s=k(r,"field");if(!s)continue;let a=e.targetTable??t.nodes.find(l=>l.id===e.to)?.name;if(!a)continue;let c=o.get(a);if(!c||c.includes(s))continue;let d=le(s,c);n.push({severity:"error",code:"FG-001",message:`"${r.name}" references field "${s}" on table "${a}" but no such field exists`,artifact:r.name,table:a,file:r.source.file,suggestion:d?`Did you mean "${d}"?`:void 0})}return n}function ge(t){let i=[],n=new Map;for(let o of t.edges){if(o.type!=="dependency")continue;let e=t.nodes.find(d=>d.id===o.from);if(!e?.source.file)continue;let r=o.targetTable??t.nodes.find(d=>d.id===o.to)?.name??"unknown",s=k(e,"type")??"unknown",a=`${r}::${s}`,c=n.get(a)??[];c.push(e),n.set(a,c)}for(let[o,e]of n){let r=new Map;for(let s of e){let a=de(s,"order")??100,c=r.get(a)??[];c.push(s),r.set(a,c)}for(let[s,a]of r){if(a.length<2)continue;let[c,d]=o.split("::"),l=a.map(y=>y.name).join(" and ");i.push({severity:"warning",code:"FG-002",message:`${l} (order: ${s}) both fire "${d}" on "${c}" \u2014 nondeterministic execution order`,table:c,file:a[0].source.file})}}return i}function pe(t){let i=[],n=new Set(t.nodes.map(o=>o.id));for(let o of t.edges){if(o.type!=="schema_relationship"||o.to.startsWith("platform:")||n.has(o.to))continue;let e=t.nodes.find(a=>a.id===o.from);if(!e)continue;let r=o.to,s=k(e,"element");i.push({severity:"warning",code:"FG-003",message:`Field "${s??o.via??"unknown"}" on "${e.name}" references "${r}" but this table is not in the local project or known platform tables`,artifact:e.name,file:e.source.file})}return i}function me(t){let i=[],n=new Map;for(let o of t.nodes){let e=`${o.table}::${o.name}`,r=n.get(e)??[];r.push(o),n.set(e,r)}for(let[o,e]of n){if(e.length<2)continue;let[r,s]=o.split("::");i.push({severity:"warning",code:"FG-004",message:`Two artifacts named "${s}" exist on "${r}" \u2014 the last defined wins, the other is shadowed`,artifact:s,table:r,file:e[0].source.file})}return i}function ye(t,i){let n=[],o=t.nodes.filter(e=>e.action==="DELETE");for(let e of o){let s=w(t,e.name,5).hops.filter(c=>c.node.action!=="DELETE"&&c.node.source.file);if(s.length===0)continue;let a=i.getDisplayGroupName(e.table).toLowerCase();n.push({severity:"error",code:"FG-005",message:`"${e.name}" (${a}) is marked DELETE but ${s.length} active artifact(s) still reference it \u2014 these will break after deployment`,artifact:e.name,table:e.table,file:e.source.file,suggestion:`Review: ${s.slice(0,3).map(c=>c.node.name).join(", ")}${s.length>3?" ...":""}`})}return n}function K(t,i){let n=[...ue(t,i),...ge(t),...pe(t),...me(t),...ye(t,i)],o={error:0,warning:1,info:2};n.sort((a,c)=>o[a.severity]-o[c.severity]);let e=n.filter(a=>a.severity==="error"),r=n.filter(a=>a.severity==="warning"),s=n.filter(a=>a.severity==="info");return{valid:e.length===0,issueCount:n.length,errors:e,warnings:r,info:s}}0&&(module.exports={extractLineageGraph,selectContext,traverseBlastRadius,validateGraph});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yesprasad/fluent-graph",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Dependency visibility and impact analysis for ServiceNow Fluent SDK projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fluent-graph": "./bin/fluent-graph.js"
|
|
@@ -41,7 +41,6 @@
|
|
|
41
41
|
"chalk": "^4.1.2",
|
|
42
42
|
"cli-table3": "^0.6.5",
|
|
43
43
|
"commander": "^12.0.0",
|
|
44
|
-
"login": "^0.8.0",
|
|
45
44
|
"ora": "^5.4.1"
|
|
46
45
|
},
|
|
47
46
|
"devDependencies": {
|