septum 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +38 -37
  2. package/package.json +6 -5
  3. package/src/cli/commands/check.ts +34 -122
  4. package/src/cli/commands/hook.ts +68 -16
  5. package/src/cli/commands/init.ts +440 -121
  6. package/src/cli/commands/sync.ts +27 -17
  7. package/src/cli/commands/topology.ts +89 -47
  8. package/src/cli/index.ts +7 -68
  9. package/src/cli/scaffold/harness-config.ts +697 -0
  10. package/src/cli/templates/hooks.ts +6 -6
  11. package/src/cli/templates/rules.ts +8 -8
  12. package/src/cli/templates/skill.ts +11 -18
  13. package/src/cli/ui/prompts.ts +122 -0
  14. package/src/core/boundary/evaluator.ts +21 -25
  15. package/src/core/boundary/staged-auditor.ts +50 -0
  16. package/src/core/config/loader.ts +21 -24
  17. package/src/core/database/client.ts +8 -0
  18. package/src/core/database/repositories/dependency.repository.ts +80 -16
  19. package/src/core/database/repositories/domain.repository.ts +86 -33
  20. package/src/core/database/repositories/symbol.repository.ts +147 -65
  21. package/src/core/database/repository.ts +17 -5
  22. package/src/core/database/schema.ts +1 -1
  23. package/src/core/discovery/topology-detector.ts +71 -19
  24. package/src/core/ingestion/pipeline.ts +274 -126
  25. package/src/core/parser/extractors/frontend.ts +1 -0
  26. package/src/core/parser/extractors/go.ts +2 -2
  27. package/src/core/parser/extractors/laravel-semantic.ts +9 -8
  28. package/src/core/parser/extractors/mcp-cli-semantic.ts +56 -16
  29. package/src/core/parser/extractors/nestjs-semantic.ts +3 -2
  30. package/src/core/parser/extractors/php.ts +2 -2
  31. package/src/core/parser/extractors/python.ts +2 -2
  32. package/src/core/parser/extractors/typescript.ts +2 -2
  33. package/src/core/parser/schema/providers/laravel-migration-provider.ts +4 -3
  34. package/src/core/parser/schema/providers/prisma-schema-provider.ts +2 -1
  35. package/src/core/parser/tree-sitter.ts +6 -1
  36. package/src/core/parser/wasm-assets.ts +17 -0
  37. package/src/core/parser/wasm-loader.ts +3 -0
  38. package/src/core/resolver/call-graph-tracer.ts +4 -3
  39. package/src/core/resolver/module-resolver.ts +1 -10
  40. package/src/core/resolver/path-resolver.ts +57 -4
  41. package/src/core/resolver/vertical-slice-tracer.ts +1 -0
  42. package/src/core/resources/ignore.ts +35 -0
  43. package/src/core/resources/limits.ts +14 -0
  44. package/src/core/resources/source-files.ts +62 -0
  45. package/src/core/resources/work-queue.ts +74 -0
  46. package/src/core/session/session-manager.ts +67 -38
  47. package/src/core/telemetry/telemetry.ts +12 -1
  48. package/src/core/topology/environment-detector.ts +21 -11
  49. package/src/mcp/schemas.ts +381 -8
  50. package/src/mcp/server.ts +326 -529
  51. package/src/mcp/tools/boundary.ts +17 -0
  52. package/src/mcp/tools/check-boundary.ts +97 -43
  53. package/src/mcp/tools/domain.ts +40 -0
  54. package/src/mcp/tools/feature.ts +67 -0
  55. package/src/mcp/tools/get-domain-catalog.ts +15 -7
  56. package/src/mcp/tools/get-environment-topology.ts +2 -2
  57. package/src/mcp/tools/get-feature-context.ts +9 -12
  58. package/src/mcp/tools/get-symbol-impact.ts +5 -2
  59. package/src/mcp/tools/get-symbol.ts +2 -1
  60. package/src/mcp/tools/locate-symbol.ts +28 -5
  61. package/src/mcp/tools/register-domain.ts +25 -9
  62. package/src/mcp/tools/register-feature.ts +40 -21
  63. package/src/mcp/tools/slice.ts +15 -0
  64. package/src/mcp/tools/symbol.ts +82 -0
  65. package/src/mcp/workspace-manager.ts +159 -0
  66. package/src/mcp/workspace-watcher.ts +202 -0
  67. package/src/types/index.ts +21 -1
  68. package/src/types/wasm.d.ts +4 -0
  69. package/src/cli/commands/ingest.ts +0 -29
  70. package/src/cli/commands/locate.ts +0 -61
  71. package/src/cli/commands/query.ts +0 -104
  72. package/src/cli/commands/slice.ts +0 -50
@@ -157,11 +157,12 @@ export function normalizeBoundaryArgs(raw: unknown): unknown {
157
157
  obj.file_path = obj.file_paths;
158
158
  delete obj.file_paths;
159
159
  }
160
- if (obj.staged === true && obj.scope === undefined) {
161
- obj.scope = "staged";
162
- }
163
- if (obj.domain !== undefined && obj.file_path === undefined && obj.file_paths === undefined) {
164
- obj.scope = "domain";
160
+ if (obj.scope === undefined) {
161
+ if (obj.staged === true) {
162
+ obj.scope = "staged";
163
+ } else if (obj.domain !== undefined && obj.file_path === undefined && obj.file_paths === undefined) {
164
+ obj.scope = "domain";
165
+ }
165
166
  }
166
167
  return obj;
167
168
  }
@@ -387,6 +388,7 @@ export const GetSymbolImpactSchema = z.preprocess(
387
388
  z.object({
388
389
  symbol: z.string().min(1, "symbol is required"),
389
390
  query: z.string().optional(),
391
+ domain: z.string().optional(),
390
392
  workspace_path: z.string().optional(),
391
393
  })
392
394
  );
@@ -443,14 +445,39 @@ export type TraceVerticalSliceArgs = z.infer<typeof TraceVerticalSliceSchema>;
443
445
  export const CheckBoundarySchema = z.preprocess(
444
446
  normalizeBoundaryArgs,
445
447
  z.object({
446
- file_path: z.string().optional(),
447
- file_paths: z.array(z.string()).optional(),
448
+ file_path: z.string().trim().min(1, "file_path cannot be blank").optional(),
449
+ file_paths: z.array(z.string().trim().min(1, "file_paths cannot contain blank paths")).optional(),
448
450
  proposed_imports: z.array(z.string()).optional().default([]),
449
451
  feature_key: z.string().optional(),
450
452
  scope: z.enum(["file", "all", "staged", "domain"]).optional(),
451
- domain: z.string().optional(),
453
+ domain: z.string().trim().min(1, "domain cannot be blank").optional(),
452
454
  staged: z.boolean().optional(),
453
455
  workspace_path: z.string().optional(),
456
+ }).superRefine((args, ctx) => {
457
+ const hasBatch = Boolean(args.file_paths?.length);
458
+ const hasTarget = args.file_path !== undefined || hasBatch;
459
+ const scope = args.scope ?? (args.staged ? "staged" : hasTarget ? "file" : args.domain ? "domain" : "all");
460
+ if (args.file_path !== undefined && hasBatch) {
461
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["file_paths"], message: "Use either file_path or file_paths, not both." });
462
+ }
463
+ if (args.staged && scope !== "staged") {
464
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["staged"], message: "staged=true requires scope='staged'." });
465
+ }
466
+ if (scope === "file" && !hasTarget) {
467
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["file_path"], message: "scope='file' requires file_path or non-empty file_paths." });
468
+ }
469
+ if (scope !== "file" && hasTarget) {
470
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["scope"], message: `scope='${scope}' does not accept file targets. Use scope='file' for proposed-import checks.` });
471
+ }
472
+ if (scope === "domain" && !args.domain) {
473
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["domain"], message: "scope='domain' requires a registered domain name." });
474
+ }
475
+ if (scope === "all" && args.domain) {
476
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["domain"], message: "scope='all' checks the entire repository. Use scope='domain' to filter a domain." });
477
+ }
478
+ if (scope !== "file" && args.proposed_imports.length) {
479
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["proposed_imports"], message: "proposed_imports requires a file target and scope='file'." });
480
+ }
454
481
  })
455
482
  );
456
483
  export type CheckBoundaryArgs = z.infer<typeof CheckBoundarySchema>;
@@ -693,3 +720,349 @@ export const GetEnvironmentTopologySchema = z.preprocess(
693
720
  })
694
721
  );
695
722
  export type GetEnvironmentTopologyArgs = z.infer<typeof GetEnvironmentTopologySchema>;
723
+
724
+ // =========================================================================
725
+ // 5 UNIFIED TOOLS SCHEMAS & NORMALIZERS (Refactored Clean-Break MCP Engine)
726
+ // =========================================================================
727
+
728
+ // 1. septum_domain
729
+ export function normalizeDomainToolArgs(raw: unknown): unknown {
730
+ const coerced = extractSingleStringFallback(raw, "domain", [
731
+ "workspace_path",
732
+ "workspacePath",
733
+ "workspace",
734
+ "project_root",
735
+ "projectRoot",
736
+ "target_path",
737
+ "path",
738
+ "action",
739
+ "detail_level",
740
+ "detailLevel",
741
+ "mode",
742
+ "format",
743
+ "archetype_filter",
744
+ "archetype",
745
+ "file_filter",
746
+ "fileFilter",
747
+ "root",
748
+ "root_path",
749
+ "rootPath",
750
+ ]);
751
+ if (typeof coerced !== "object" || coerced === null || Array.isArray(coerced)) return coerced;
752
+ const obj = withAliases(coerced, {
753
+ domain_name: "domain",
754
+ domainName: "domain",
755
+ name: "domain",
756
+ archetype: "archetype_filter",
757
+ archetypeFilter: "archetype_filter",
758
+ type: "archetype_filter",
759
+ detailLevel: "detail_level",
760
+ mode: "detail_level",
761
+ format: "detail_level",
762
+ fileFilter: "file_filter",
763
+ file: "file_filter",
764
+ filter: "file_filter",
765
+ root_path: "root",
766
+ rootPath: "root",
767
+ path: "root",
768
+ allowedDependencies: "allowed_dependencies",
769
+ allowed: "allowed_dependencies",
770
+ forbiddenDependencies: "forbidden_dependencies",
771
+ forbidden: "forbidden_dependencies",
772
+ archetypes: "archetypes",
773
+ ingestNow: "ingest_now",
774
+ resolveLayer: "resolve",
775
+ resolve_layer: "resolve",
776
+ resolveNode: "resolve_node",
777
+ workspace: "workspace_path",
778
+ workspacePath: "workspace_path",
779
+ project_root: "workspace_path",
780
+ projectRoot: "workspace_path",
781
+ }) as Record<string, unknown>;
782
+
783
+ if (obj.domain !== undefined && obj.name === undefined) {
784
+ obj.name = obj.domain;
785
+ } else if (obj.name !== undefined && obj.domain === undefined) {
786
+ obj.domain = obj.name;
787
+ }
788
+
789
+ if (obj.archetype_filter !== undefined && obj.archetype === undefined) {
790
+ obj.archetype = obj.archetype_filter;
791
+ } else if (obj.archetype !== undefined && obj.archetype_filter === undefined) {
792
+ obj.archetype_filter = obj.archetype;
793
+ }
794
+
795
+ if (obj.full === true && obj.detail_level === undefined) {
796
+ obj.detail_level = "full";
797
+ }
798
+
799
+ if (typeof obj.resolve_node === "object" && obj.resolve_node !== null) {
800
+ obj.resolve_node = withAliases(obj.resolve_node, {
801
+ canonicalTag: "canonical_tag",
802
+ domainOrIp: "domain_or_ip",
803
+ connectedTo: "connected_to",
804
+ nodeId: "id",
805
+ node_id: "id",
806
+ nodeName: "name",
807
+ node_name: "name",
808
+ });
809
+ }
810
+
811
+ if (!obj.action) {
812
+ if (obj.root !== undefined || obj.ingest_now !== undefined) {
813
+ obj.action = "register";
814
+ } else if (obj.resolve !== undefined || obj.resolve_node !== undefined || obj.resolveNode !== undefined || obj.refresh !== undefined) {
815
+ obj.action = "topology";
816
+ } else {
817
+ obj.action = "catalog";
818
+ }
819
+ }
820
+
821
+ return obj;
822
+ }
823
+
824
+ export const DomainToolSchema = z.preprocess(
825
+ normalizeDomainToolArgs,
826
+ z.object({
827
+ action: z.enum(["catalog", "register", "topology"]).optional().default("catalog"),
828
+ domain: z.string().optional(),
829
+ name: z.string().optional(),
830
+ detail_level: z.enum(["compact", "full"]).optional().default("compact"),
831
+ archetype_filter: z.string().optional(),
832
+ archetype: z.string().optional(),
833
+ file_filter: z.string().optional(),
834
+ root: z.string().optional(),
835
+ description: z.string().optional(),
836
+ allowed_dependencies: z.array(z.string()).optional(),
837
+ forbidden_dependencies: z.array(z.string()).optional(),
838
+ archetypes: z.record(z.string()).optional(),
839
+ ingest_now: z.boolean().optional().default(false),
840
+ resolve: z
841
+ .object({
842
+ layer: z.enum(["edge", "gateway", "host", "runtime", "storage", "telemetry"]),
843
+ platform: z.string().min(1, "platform name cannot be empty"),
844
+ constraints: z.array(z.string()).optional(),
845
+ })
846
+ .optional(),
847
+ resolve_node: z
848
+ .object({
849
+ id: z.string().min(1, "Node id cannot be empty"),
850
+ layer: z.enum(["edge", "gateway", "host", "runtime", "storage", "telemetry"]),
851
+ name: z.string().min(1, "Node name cannot be empty"),
852
+ platform: z.string().min(1, "Platform cannot be empty"),
853
+ canonical_tag: z
854
+ .enum([
855
+ "shared_hosting",
856
+ "vps",
857
+ "docker",
858
+ "serverless",
859
+ "kubernetes",
860
+ "managed_db",
861
+ "cdn_edge",
862
+ "paas",
863
+ "static_cdn",
864
+ "unspecified",
865
+ ])
866
+ .optional(),
867
+ domain_or_ip: z.string().optional(),
868
+ role: z.string().optional(),
869
+ constraints: z.array(z.string()).optional(),
870
+ connected_to: z.array(z.string()).optional(),
871
+ })
872
+ .optional(),
873
+ resolveNode: z.any().optional(),
874
+ refresh: z.boolean().optional().default(false),
875
+ workspace_path: z.string().optional(),
876
+ })
877
+ );
878
+ export type DomainToolArgs = z.infer<typeof DomainToolSchema>;
879
+
880
+ // 2. septum_symbol
881
+ export function normalizeSymbolToolArgs(raw: unknown): unknown {
882
+ const coerced = extractSingleStringFallback(raw, "query", [
883
+ "workspace_path",
884
+ "workspacePath",
885
+ "workspace",
886
+ "project_root",
887
+ "projectRoot",
888
+ "action",
889
+ "domain",
890
+ "domain_name",
891
+ "domainName",
892
+ "min_lines",
893
+ "min_nesting",
894
+ "sort_by",
895
+ "limit",
896
+ ]);
897
+ if (typeof coerced !== "object" || coerced === null || Array.isArray(coerced)) return coerced;
898
+ const obj = withAliases(coerced, {
899
+ symbol: "query",
900
+ name: "query",
901
+ symbol_name: "query",
902
+ symbolName: "query",
903
+ target: "query",
904
+ domain_name: "domain",
905
+ domainName: "domain",
906
+ includeDependencies: "include_dependencies",
907
+ include_deps: "include_dependencies",
908
+ includeDeps: "include_dependencies",
909
+ dependencies: "include_dependencies",
910
+ deps: "include_dependencies",
911
+ max: "limit",
912
+ nesting: "min_nesting",
913
+ minNesting: "min_nesting",
914
+ lines: "min_lines",
915
+ minLines: "min_lines",
916
+ sortBy: "sort_by",
917
+ workspace: "workspace_path",
918
+ workspacePath: "workspace_path",
919
+ project_root: "workspace_path",
920
+ projectRoot: "workspace_path",
921
+ }) as Record<string, unknown>;
922
+
923
+ if (obj.query !== undefined && obj.symbol === undefined) {
924
+ obj.symbol = obj.query;
925
+ } else if (obj.symbol !== undefined && obj.query === undefined) {
926
+ obj.query = obj.symbol;
927
+ }
928
+
929
+ if (!obj.action) {
930
+ if (obj.min_lines !== undefined || obj.min_nesting !== undefined || obj.kind !== undefined || (obj.sort_by && !obj.query)) {
931
+ obj.action = "hotspots";
932
+ } else if (obj.include_dependencies !== undefined) {
933
+ obj.action = "inspect";
934
+ } else {
935
+ obj.action = "locate";
936
+ }
937
+ }
938
+
939
+ return obj;
940
+ }
941
+
942
+ export const SymbolToolSchema = z.preprocess(
943
+ normalizeSymbolToolArgs,
944
+ z.object({
945
+ action: z.enum(["locate", "inspect", "impact", "hotspots"]).optional().default("locate"),
946
+ query: z.string().optional(),
947
+ symbol: z.string().optional(),
948
+ domain: z.string().optional(),
949
+ include_dependencies: z.boolean().optional().default(true),
950
+ limit: z.number().int().positive().optional(),
951
+ min_lines: z.number().int().nonnegative().optional(),
952
+ min_nesting: z.number().int().positive().optional(),
953
+ kind: z.string().optional(),
954
+ sort_by: z.enum(["lines", "nesting", "risk_score", "line_count"]).optional(),
955
+ workspace_path: z.string().optional(),
956
+ })
957
+ );
958
+ export type SymbolToolArgs = z.infer<typeof SymbolToolSchema>;
959
+
960
+ // 3. septum_slice
961
+ export const SliceToolSchema = TraceVerticalSliceSchema;
962
+ export type SliceToolArgs = TraceVerticalSliceArgs;
963
+
964
+ // 4. septum_boundary
965
+ export const BoundaryToolSchema = CheckBoundarySchema;
966
+ export type BoundaryToolArgs = CheckBoundaryArgs;
967
+
968
+ // 5. septum_feature
969
+ export function normalizeFeatureToolArgs(raw: unknown): unknown {
970
+ const coerced = extractSingleStringFallback(raw, "feature_key", [
971
+ "workspace_path",
972
+ "workspacePath",
973
+ "workspace",
974
+ "project_root",
975
+ "projectRoot",
976
+ "action",
977
+ "domain",
978
+ "domain_name",
979
+ "domainName",
980
+ "force",
981
+ ]);
982
+ if (typeof coerced !== "object" || coerced === null || Array.isArray(coerced)) return coerced;
983
+ const obj = { ...(coerced as Record<string, unknown>) };
984
+
985
+ const aliases: Record<string, string> = {
986
+ feature: "feature_key",
987
+ featureKey: "feature_key",
988
+ feature_name: "feature_key",
989
+ featureName: "feature_key",
990
+ feat: "feature_key",
991
+ name: "feature_key",
992
+ key: "feature_key",
993
+ domain_name: "domain",
994
+ domainName: "domain",
995
+ touchpoints: "allowed_touchpoints",
996
+ allowedTouchpoints: "allowed_touchpoints",
997
+ touchpoint: "allowed_touchpoints",
998
+ files: "allowed_touchpoints",
999
+ allowed_files: "allowed_touchpoints",
1000
+ reuseSymbols: "reuse_symbols",
1001
+ reuse: "reuse_symbols",
1002
+ symbols: "reuse_symbols",
1003
+ inputContract: "input_contract",
1004
+ outputContract: "output_contract",
1005
+ workspace: "workspace_path",
1006
+ workspacePath: "workspace_path",
1007
+ project_root: "workspace_path",
1008
+ projectRoot: "workspace_path",
1009
+ };
1010
+
1011
+ for (const [alias, target] of Object.entries(aliases)) {
1012
+ if (obj[alias] !== undefined && obj[target] === undefined) {
1013
+ obj[target] = obj[alias];
1014
+ }
1015
+ }
1016
+
1017
+ if (obj.feature_key !== undefined && obj.feature === undefined) {
1018
+ obj.feature = obj.feature_key;
1019
+ } else if (obj.feature !== undefined && obj.feature_key === undefined) {
1020
+ obj.feature_key = obj.feature;
1021
+ }
1022
+
1023
+ if (obj.domain !== undefined && obj.domain_name === undefined) {
1024
+ obj.domain_name = obj.domain;
1025
+ } else if (obj.domain_name !== undefined && obj.domain === undefined) {
1026
+ obj.domain = obj.domain_name;
1027
+ }
1028
+
1029
+ if (typeof obj.allowed_touchpoints === "string") {
1030
+ obj.allowed_touchpoints = [obj.allowed_touchpoints];
1031
+ } else if (!Array.isArray(obj.allowed_touchpoints) && obj.allowed_touchpoints !== undefined) {
1032
+ obj.allowed_touchpoints = [];
1033
+ }
1034
+
1035
+ if (typeof obj.reuse_symbols === "string") {
1036
+ obj.reuse_symbols = [obj.reuse_symbols];
1037
+ }
1038
+
1039
+ if (!obj.action) {
1040
+ if (obj.allowed_touchpoints !== undefined || obj.domain !== undefined || obj.reuse_symbols !== undefined) {
1041
+ obj.action = "lock";
1042
+ } else {
1043
+ obj.action = "get";
1044
+ }
1045
+ }
1046
+
1047
+ return obj;
1048
+ }
1049
+
1050
+ export const FeatureToolSchema = z.preprocess(
1051
+ normalizeFeatureToolArgs,
1052
+ z.object({
1053
+ action: z.enum(["get", "lock", "release"]).optional().default("get"),
1054
+ feature_key: z.string().optional(),
1055
+ feature: z.string().optional(),
1056
+ domain: z.string().optional(),
1057
+ domain_name: z.string().optional(),
1058
+ description: z.string().optional(),
1059
+ allowed_touchpoints: z.array(z.string()).optional(),
1060
+ reuse_symbols: z.array(z.string()).optional(),
1061
+ input_contract: z.record(z.unknown()).optional(),
1062
+ output_contract: z.record(z.unknown()).optional(),
1063
+ force: z.boolean().optional().default(false),
1064
+ workspace_path: z.string().optional(),
1065
+ })
1066
+ );
1067
+ export type FeatureToolArgs = z.infer<typeof FeatureToolSchema>;
1068
+