@ijfw/install 1.3.2 → 1.4.1
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 +30 -0
- package/dist/ijfw.js +1124 -105
- package/dist/install.js +66 -11
- package/package.json +1 -1
package/dist/ijfw.js
CHANGED
|
@@ -686,6 +686,996 @@ var init_gitleaks = __esm({
|
|
|
686
686
|
}
|
|
687
687
|
});
|
|
688
688
|
|
|
689
|
+
// ../mcp-server/src/gate-result-schema.js
|
|
690
|
+
function makeGateId(gate) {
|
|
691
|
+
if (typeof gate !== "string" || !GATE_NAME_PATTERN.test(gate)) {
|
|
692
|
+
throw new TypeError(
|
|
693
|
+
`makeGateId: invalid gate name "${gate}" \u2014 must match ${GATE_NAME_PATTERN}`
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
const safe = gate.replace(/:/g, "-");
|
|
697
|
+
const ts = Date.now();
|
|
698
|
+
const rand4 = Math.floor(Math.random() * 65536).toString(16).padStart(4, "0");
|
|
699
|
+
return `${safe}-${ts}-${rand4}`;
|
|
700
|
+
}
|
|
701
|
+
function isString(v2) {
|
|
702
|
+
return typeof v2 === "string";
|
|
703
|
+
}
|
|
704
|
+
function isNonNullObject(v2) {
|
|
705
|
+
return v2 !== null && typeof v2 === "object" && !Array.isArray(v2);
|
|
706
|
+
}
|
|
707
|
+
function validateGateResult(obj) {
|
|
708
|
+
const errors = [];
|
|
709
|
+
if (!isNonNullObject(obj)) {
|
|
710
|
+
return { valid: false, errors: ["root: must be an object"] };
|
|
711
|
+
}
|
|
712
|
+
if (obj.schema_version !== SCHEMA_VERSION) {
|
|
713
|
+
errors.push(
|
|
714
|
+
`schema_version: must equal "${SCHEMA_VERSION}", got ${JSON.stringify(obj.schema_version)}`
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
if (!isString(obj.gate)) {
|
|
718
|
+
errors.push("gate: must be a string");
|
|
719
|
+
} else if (!GATE_NAME_PATTERN.test(obj.gate)) {
|
|
720
|
+
errors.push(
|
|
721
|
+
`gate: "${obj.gate}" does not match ${GATE_NAME_PATTERN}`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
if (!VALID_STATUSES.includes(obj.status)) {
|
|
725
|
+
errors.push(
|
|
726
|
+
`status: must be one of ${VALID_STATUSES.join("|")}, got ${JSON.stringify(obj.status)}`
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
if (!VALID_PROJECT_TYPES.includes(obj.project_type)) {
|
|
730
|
+
errors.push(
|
|
731
|
+
`project_type: must be one of ${VALID_PROJECT_TYPES.join("|")}, got ${JSON.stringify(obj.project_type)}`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
if (!Array.isArray(obj.lenses)) {
|
|
735
|
+
errors.push("lenses: must be an array (empty for single-model gates)");
|
|
736
|
+
} else {
|
|
737
|
+
obj.lenses.forEach((lens, i) => {
|
|
738
|
+
if (!isNonNullObject(lens)) {
|
|
739
|
+
errors.push(`lenses[${i}]: must be an object`);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (!isString(lens.model)) errors.push(`lenses[${i}].model: must be a string`);
|
|
743
|
+
if (!VALID_STATUSES.includes(lens.verdict)) {
|
|
744
|
+
errors.push(
|
|
745
|
+
`lenses[${i}].verdict: must be one of ${VALID_STATUSES.join("|")}`
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
if (typeof lens.confidence !== "number" || lens.confidence < 0 || lens.confidence > 1) {
|
|
749
|
+
errors.push(`lenses[${i}].confidence: must be number in [0,1]`);
|
|
750
|
+
}
|
|
751
|
+
if (!isString(lens.summary)) errors.push(`lenses[${i}].summary: must be a string`);
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
if (!Array.isArray(obj.affected_artifacts)) {
|
|
755
|
+
errors.push("affected_artifacts: must be an array");
|
|
756
|
+
} else {
|
|
757
|
+
obj.affected_artifacts.forEach((a, i) => {
|
|
758
|
+
if (!isNonNullObject(a)) {
|
|
759
|
+
errors.push(`affected_artifacts[${i}]: must be an object`);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (!VALID_ARTIFACT_TYPES.includes(a.type)) {
|
|
763
|
+
errors.push(
|
|
764
|
+
`affected_artifacts[${i}].type: must be one of ${VALID_ARTIFACT_TYPES.join("|")}`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
if (!isString(a.ref)) errors.push(`affected_artifacts[${i}].ref: must be a string`);
|
|
768
|
+
if (!isString(a.role)) errors.push(`affected_artifacts[${i}].role: must be a string`);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
if (!isNonNullObject(obj.accounting)) {
|
|
772
|
+
errors.push("accounting: must be an object");
|
|
773
|
+
} else {
|
|
774
|
+
const a = obj.accounting;
|
|
775
|
+
if (typeof a.duration_ms !== "number" || a.duration_ms < 0) {
|
|
776
|
+
errors.push("accounting.duration_ms: must be a non-negative number");
|
|
777
|
+
}
|
|
778
|
+
if (typeof a.lenses_invoked !== "number" || a.lenses_invoked < 0 || !Number.isInteger(a.lenses_invoked)) {
|
|
779
|
+
errors.push("accounting.lenses_invoked: must be a non-negative integer");
|
|
780
|
+
}
|
|
781
|
+
if (a.cost_usd !== null && (typeof a.cost_usd !== "number" || a.cost_usd < 0)) {
|
|
782
|
+
errors.push("accounting.cost_usd: must be null or non-negative number");
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (!Array.isArray(obj.remediation)) {
|
|
786
|
+
errors.push("remediation: must be an array (may be empty)");
|
|
787
|
+
} else {
|
|
788
|
+
obj.remediation.forEach((r, i) => {
|
|
789
|
+
if (!isNonNullObject(r)) {
|
|
790
|
+
errors.push(`remediation[${i}]: must be an object`);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (!isString(r.action)) errors.push(`remediation[${i}].action: must be a string`);
|
|
794
|
+
if (!isString(r.target)) errors.push(`remediation[${i}].target: must be a string`);
|
|
795
|
+
if (!isString(r.agent_recommended)) {
|
|
796
|
+
errors.push(`remediation[${i}].agent_recommended: must be a string`);
|
|
797
|
+
}
|
|
798
|
+
if (typeof r.confidence !== "number" || r.confidence < 0 || r.confidence > 1) {
|
|
799
|
+
errors.push(`remediation[${i}].confidence: must be number in [0,1]`);
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
if (obj.receipts_ref !== null && !isString(obj.receipts_ref)) {
|
|
804
|
+
errors.push("receipts_ref: must be a string or null");
|
|
805
|
+
}
|
|
806
|
+
if (obj.supersedes !== null && !isString(obj.supersedes)) {
|
|
807
|
+
errors.push("supersedes: must be a string or null");
|
|
808
|
+
}
|
|
809
|
+
if (!isString(obj.gate_id) || obj.gate_id.length === 0) {
|
|
810
|
+
errors.push("gate_id: must be a non-empty string");
|
|
811
|
+
} else if (isString(obj.gate)) {
|
|
812
|
+
const safeGate = obj.gate.replace(/:/g, "-");
|
|
813
|
+
if (!obj.gate_id.startsWith(safeGate + "-")) {
|
|
814
|
+
errors.push(
|
|
815
|
+
`gate_id: expected to start with "${safeGate}-" (colon-collapsed gate name)`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (!isString(obj.emitted_at) || !ISO8601_PATTERN.test(obj.emitted_at)) {
|
|
820
|
+
errors.push("emitted_at: must be ISO-8601 string");
|
|
821
|
+
}
|
|
822
|
+
return { valid: errors.length === 0, errors };
|
|
823
|
+
}
|
|
824
|
+
function formatGateResult(obj) {
|
|
825
|
+
const json = JSON.stringify(obj, null, 2);
|
|
826
|
+
return "```gate-result\n" + json + "\n```";
|
|
827
|
+
}
|
|
828
|
+
var SCHEMA_VERSION, GATE_NAME_PATTERN, VALID_STATUSES, VALID_PROJECT_TYPES, VALID_ARTIFACT_TYPES, ISO8601_PATTERN;
|
|
829
|
+
var init_gate_result_schema = __esm({
|
|
830
|
+
"../mcp-server/src/gate-result-schema.js"() {
|
|
831
|
+
SCHEMA_VERSION = "1.0";
|
|
832
|
+
GATE_NAME_PATTERN = /^[a-z][a-z0-9-]*(:[a-z][a-z0-9-]*)?$/;
|
|
833
|
+
VALID_STATUSES = Object.freeze([
|
|
834
|
+
"PASS",
|
|
835
|
+
"CONDITIONAL",
|
|
836
|
+
"WARN",
|
|
837
|
+
"FLAG",
|
|
838
|
+
"FAIL"
|
|
839
|
+
]);
|
|
840
|
+
VALID_PROJECT_TYPES = Object.freeze([
|
|
841
|
+
"software",
|
|
842
|
+
"book",
|
|
843
|
+
"content",
|
|
844
|
+
"business",
|
|
845
|
+
"design",
|
|
846
|
+
"mixed",
|
|
847
|
+
"unknown"
|
|
848
|
+
]);
|
|
849
|
+
VALID_ARTIFACT_TYPES = Object.freeze([
|
|
850
|
+
"file",
|
|
851
|
+
"chapter",
|
|
852
|
+
"section",
|
|
853
|
+
"asset",
|
|
854
|
+
"persona",
|
|
855
|
+
"decision",
|
|
856
|
+
"component"
|
|
857
|
+
]);
|
|
858
|
+
ISO8601_PATTERN = // eslint-disable-next-line security/detect-unsafe-regex -- fixed-length anchored ISO 8601 shape; optional fractional + tz are non-overlapping
|
|
859
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// ../mcp-server/src/scan-resume.js
|
|
864
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, unlinkSync, copyFileSync } from "fs";
|
|
865
|
+
import { join as join4 } from "path";
|
|
866
|
+
function statePath(projectRoot) {
|
|
867
|
+
return join4(String(projectRoot), ".ijfw", STATE_FILE);
|
|
868
|
+
}
|
|
869
|
+
function loadScanState(projectRoot) {
|
|
870
|
+
const path = statePath(projectRoot);
|
|
871
|
+
if (!existsSync(path)) return null;
|
|
872
|
+
try {
|
|
873
|
+
const raw = readFileSync2(path, "utf8");
|
|
874
|
+
const parsed = JSON.parse(raw);
|
|
875
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
876
|
+
} catch {
|
|
877
|
+
}
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
function writeScanState(projectRoot, state) {
|
|
881
|
+
const dir = join4(String(projectRoot), ".ijfw");
|
|
882
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
883
|
+
const finalPath = statePath(projectRoot);
|
|
884
|
+
const tmpPath = `${finalPath}.tmp.${process.pid}.${Date.now()}`;
|
|
885
|
+
const safe = {
|
|
886
|
+
scan_id: String(state.scan_id || ""),
|
|
887
|
+
started_at: String(state.started_at || (/* @__PURE__ */ new Date()).toISOString()),
|
|
888
|
+
last_path_walked: String(state.last_path_walked || ""),
|
|
889
|
+
files_scanned: Number.isFinite(state.files_scanned) ? state.files_scanned : 0,
|
|
890
|
+
total_estimate: Number.isFinite(state.total_estimate) ? state.total_estimate : 0,
|
|
891
|
+
attempts: Number.isFinite(state.attempts) ? state.attempts : 1,
|
|
892
|
+
incomplete: state.incomplete !== false,
|
|
893
|
+
session_id: state.session_id || null
|
|
894
|
+
};
|
|
895
|
+
if (state.partial && typeof state.partial === "object") {
|
|
896
|
+
safe.partial = state.partial;
|
|
897
|
+
}
|
|
898
|
+
writeFileSync2(tmpPath, JSON.stringify(safe, null, 2) + "\n", "utf8");
|
|
899
|
+
try {
|
|
900
|
+
renameSync(tmpPath, finalPath);
|
|
901
|
+
} catch (err) {
|
|
902
|
+
if (!err || err.code !== "EXDEV") throw err;
|
|
903
|
+
try {
|
|
904
|
+
copyFileSync(tmpPath, finalPath);
|
|
905
|
+
} finally {
|
|
906
|
+
try {
|
|
907
|
+
unlinkSync(tmpPath);
|
|
908
|
+
} catch {
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return finalPath;
|
|
913
|
+
}
|
|
914
|
+
function lockPath(projectRoot) {
|
|
915
|
+
return join4(String(projectRoot), ".ijfw", LOCK_FILE);
|
|
916
|
+
}
|
|
917
|
+
function isPidAlive(pid) {
|
|
918
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
919
|
+
try {
|
|
920
|
+
process.kill(pid, 0);
|
|
921
|
+
return true;
|
|
922
|
+
} catch (err) {
|
|
923
|
+
if (err && err.code === "EPERM") return true;
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function reclaimIfStale(lp) {
|
|
928
|
+
if (!existsSync(lp)) return;
|
|
929
|
+
let raw;
|
|
930
|
+
try {
|
|
931
|
+
raw = readFileSync2(lp, "utf8");
|
|
932
|
+
} catch {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const lines = String(raw).split(/\r?\n/);
|
|
936
|
+
const pid = Number(lines[0]);
|
|
937
|
+
const ts = Number(lines[1]);
|
|
938
|
+
const ageOk = Number.isFinite(ts) && Date.now() - ts <= LOCK_STALE_MS;
|
|
939
|
+
if (isPidAlive(pid) && ageOk) return;
|
|
940
|
+
try {
|
|
941
|
+
unlinkSync(lp);
|
|
942
|
+
} catch {
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
function acquireScanLock(projectRoot) {
|
|
946
|
+
const dir = join4(String(projectRoot), ".ijfw");
|
|
947
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
948
|
+
const lp = lockPath(projectRoot);
|
|
949
|
+
reclaimIfStale(lp);
|
|
950
|
+
const payload = String(process.pid) + "\n" + String(Date.now()) + "\n";
|
|
951
|
+
try {
|
|
952
|
+
writeFileSync2(lp, payload, { encoding: "utf8", flag: "wx" });
|
|
953
|
+
} catch (err) {
|
|
954
|
+
if (err && err.code === "EEXIST") return null;
|
|
955
|
+
throw err;
|
|
956
|
+
}
|
|
957
|
+
let released = false;
|
|
958
|
+
return {
|
|
959
|
+
released: () => {
|
|
960
|
+
if (released) return;
|
|
961
|
+
released = true;
|
|
962
|
+
try {
|
|
963
|
+
unlinkSync(lp);
|
|
964
|
+
} catch {
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function shouldResume(state) {
|
|
970
|
+
if (!state || typeof state !== "object") return false;
|
|
971
|
+
if (state.incomplete !== true) return false;
|
|
972
|
+
if (!state.started_at || typeof state.started_at !== "string") return false;
|
|
973
|
+
const startedMs = Date.parse(state.started_at);
|
|
974
|
+
if (!Number.isFinite(startedMs)) return false;
|
|
975
|
+
const ageMs = Date.now() - startedMs;
|
|
976
|
+
if (ageMs > STALENESS_MS) return false;
|
|
977
|
+
const attempts = Number.isFinite(state.attempts) ? state.attempts : 0;
|
|
978
|
+
if (attempts >= ATTEMPT_CAP) return false;
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
function clearScanState(projectRoot) {
|
|
982
|
+
const path = statePath(projectRoot);
|
|
983
|
+
if (existsSync(path)) {
|
|
984
|
+
try {
|
|
985
|
+
unlinkSync(path);
|
|
986
|
+
} catch {
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
var STATE_FILE, LOCK_FILE, STALENESS_MS, ATTEMPT_CAP, LOCK_STALE_MS;
|
|
991
|
+
var init_scan_resume = __esm({
|
|
992
|
+
"../mcp-server/src/scan-resume.js"() {
|
|
993
|
+
STATE_FILE = "scan-state.json";
|
|
994
|
+
LOCK_FILE = "scan-state.json.lock";
|
|
995
|
+
STALENESS_MS = 24 * 60 * 60 * 1e3;
|
|
996
|
+
ATTEMPT_CAP = 3;
|
|
997
|
+
LOCK_STALE_MS = 60 * 1e3;
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
// ../mcp-server/src/project-type-detector.js
|
|
1002
|
+
import {
|
|
1003
|
+
readFileSync as readFileSync3,
|
|
1004
|
+
writeFileSync as writeFileSync3,
|
|
1005
|
+
existsSync as existsSync2,
|
|
1006
|
+
readdirSync as readdirSync3,
|
|
1007
|
+
statSync as statSync3,
|
|
1008
|
+
renameSync as renameSync2,
|
|
1009
|
+
mkdirSync as mkdirSync2,
|
|
1010
|
+
unlinkSync as unlinkSync2,
|
|
1011
|
+
realpathSync,
|
|
1012
|
+
copyFileSync as copyFileSync2
|
|
1013
|
+
} from "fs";
|
|
1014
|
+
import { join as join5, extname, isAbsolute, resolve as pathResolve, dirname } from "path";
|
|
1015
|
+
import { fileURLToPath } from "url";
|
|
1016
|
+
import { createHash } from "crypto";
|
|
1017
|
+
function detect(projectRoot, options = {}) {
|
|
1018
|
+
const root = String(projectRoot || process.cwd());
|
|
1019
|
+
const c9Available = options.c9Available === false ? false : options.c9Available === true ? true : isC9AvailableSync();
|
|
1020
|
+
const maxFiles = Number.isFinite(options.maxFiles) && options.maxFiles > 0 ? options.maxFiles : MAX_FILES;
|
|
1021
|
+
const signals = [];
|
|
1022
|
+
const fallbackReason = c9Available ? null : "c9_unavailable";
|
|
1023
|
+
if (options.explicitType && DOMAINS.includes(String(options.explicitType))) {
|
|
1024
|
+
signals.push({ kind: "user_declaration", weight: 1, value: options.explicitType });
|
|
1025
|
+
return finalize({
|
|
1026
|
+
primary: options.explicitType,
|
|
1027
|
+
secondary: [],
|
|
1028
|
+
score: 1,
|
|
1029
|
+
signals,
|
|
1030
|
+
scanIncomplete: false,
|
|
1031
|
+
fallbackReason,
|
|
1032
|
+
treeHash: "",
|
|
1033
|
+
branchHash: branchHash(root)
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
const fmAgents = readFrontmatterType(join5(root, "AGENTS.md"));
|
|
1037
|
+
if (fmAgents && DOMAINS.includes(fmAgents)) {
|
|
1038
|
+
signals.push({ kind: "agents_md_frontmatter", weight: 0.9, value: fmAgents });
|
|
1039
|
+
}
|
|
1040
|
+
const fmBrief = readFrontmatterType(join5(root, ".ijfw", "memory", "brief.md"));
|
|
1041
|
+
if (fmBrief && DOMAINS.includes(fmBrief)) {
|
|
1042
|
+
signals.push({ kind: "brief_md_frontmatter", weight: 0.8, value: fmBrief });
|
|
1043
|
+
}
|
|
1044
|
+
const timeBudgetMs = resolveTimeBudgetMs(options);
|
|
1045
|
+
const walk = walkProject(root, { maxFiles, maxDepth: MAX_DEPTH, options, timeBudgetMs });
|
|
1046
|
+
const treeHash = fileTreeHash(walk.fingerprint);
|
|
1047
|
+
if (walk.manifestsFound.length > 0) {
|
|
1048
|
+
signals.push({
|
|
1049
|
+
kind: "manifest",
|
|
1050
|
+
weight: 0.9,
|
|
1051
|
+
manifests: walk.manifestsFound.slice(0, 6)
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
for (const d of walk.dirHits.book) signals.push({ kind: "dir_book", weight: 0.4, name: d });
|
|
1055
|
+
for (const d of walk.dirHits.content) signals.push({ kind: "dir_content", weight: 0.4, name: d });
|
|
1056
|
+
for (const d of walk.dirHits.business) signals.push({ kind: "dir_business", weight: 0.4, name: d });
|
|
1057
|
+
for (const d of walk.dirHits.design) signals.push({ kind: "dir_design", weight: 0.4, name: d });
|
|
1058
|
+
const totals = walk.extTotals;
|
|
1059
|
+
const totalClassified = Object.values(totals).reduce((a, b2) => a + b2, 0);
|
|
1060
|
+
if (totalClassified > 0) {
|
|
1061
|
+
for (const [domain, count] of Object.entries(totals)) {
|
|
1062
|
+
const ratio = count / totalClassified;
|
|
1063
|
+
if (ratio >= 0.05) {
|
|
1064
|
+
signals.push({
|
|
1065
|
+
kind: "file_extension_ratio",
|
|
1066
|
+
weight: 0.7,
|
|
1067
|
+
domain,
|
|
1068
|
+
ratio: Number(ratio.toFixed(3)),
|
|
1069
|
+
count
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
for (const hit of walk.patternHits) {
|
|
1075
|
+
signals.push({ kind: "filename_pattern", weight: hit.weight, domain: hit.domain, name: hit.name });
|
|
1076
|
+
}
|
|
1077
|
+
const scoreboard = scoreSignals(signals);
|
|
1078
|
+
const ranked = rankDomains(scoreboard);
|
|
1079
|
+
let primary;
|
|
1080
|
+
let secondary = [];
|
|
1081
|
+
let confidence;
|
|
1082
|
+
if (ranked.length === 0) {
|
|
1083
|
+
primary = "unknown";
|
|
1084
|
+
confidence = 0;
|
|
1085
|
+
} else {
|
|
1086
|
+
primary = ranked[0].domain;
|
|
1087
|
+
confidence = ranked[0].score;
|
|
1088
|
+
secondary = ranked.slice(1).filter((r) => r.score >= 0.4 && r.domain !== primary).map((r) => r.domain);
|
|
1089
|
+
if (ranked.length >= 2 && ranked[0].score >= 0.55 && ranked[1].score >= 0.5 && ranked[1].score / ranked[0].score >= 0.75) {
|
|
1090
|
+
const topTwo = [ranked[0].domain, ranked[1].domain];
|
|
1091
|
+
secondary = topTwo;
|
|
1092
|
+
primary = "mixed";
|
|
1093
|
+
confidence = Math.min(0.85, (ranked[0].score + ranked[1].score) / 2);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
const highTrust = signals.some(
|
|
1097
|
+
(s) => s.kind === "user_declaration" || s.kind === "agents_md_frontmatter" || s.kind === "brief_md_frontmatter"
|
|
1098
|
+
);
|
|
1099
|
+
if (!c9Available && !highTrust && confidence > 0.7) confidence = 0.7;
|
|
1100
|
+
const scanIncomplete = walk.incomplete;
|
|
1101
|
+
if (scanIncomplete) {
|
|
1102
|
+
const lock = acquireScanLock(root);
|
|
1103
|
+
if (lock) {
|
|
1104
|
+
try {
|
|
1105
|
+
const prior = loadScanState(root) || {};
|
|
1106
|
+
writeScanState(root, {
|
|
1107
|
+
scan_id: prior.scan_id || newScanId(),
|
|
1108
|
+
started_at: prior.started_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1109
|
+
last_path_walked: walk.lastPathWalked,
|
|
1110
|
+
files_scanned: walk.filesScanned,
|
|
1111
|
+
total_estimate: walk.totalEstimate,
|
|
1112
|
+
attempts: (prior.attempts || 0) + 1,
|
|
1113
|
+
incomplete: true,
|
|
1114
|
+
session_id: options.sessionId || null,
|
|
1115
|
+
partial: snapshotPartial(walk)
|
|
1116
|
+
});
|
|
1117
|
+
} catch {
|
|
1118
|
+
} finally {
|
|
1119
|
+
lock.released();
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
} else {
|
|
1123
|
+
try {
|
|
1124
|
+
clearScanState(root);
|
|
1125
|
+
} catch {
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return finalize({
|
|
1129
|
+
primary,
|
|
1130
|
+
secondary,
|
|
1131
|
+
score: confidence,
|
|
1132
|
+
signals,
|
|
1133
|
+
scanIncomplete,
|
|
1134
|
+
fallbackReason,
|
|
1135
|
+
treeHash,
|
|
1136
|
+
branchHash: branchHash(root)
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
function finalize({ primary, secondary, score, signals, scanIncomplete, fallbackReason, treeHash, branchHash: bh }) {
|
|
1140
|
+
const confidence = Number(Math.max(0, Math.min(1, score)).toFixed(3));
|
|
1141
|
+
const out = {
|
|
1142
|
+
type: primary,
|
|
1143
|
+
// single-label alias for hoist
|
|
1144
|
+
primary_type: primary,
|
|
1145
|
+
secondary_types: Array.isArray(secondary) ? secondary : [],
|
|
1146
|
+
confidence,
|
|
1147
|
+
scan_incomplete: !!scanIncomplete,
|
|
1148
|
+
detected_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1149
|
+
signals,
|
|
1150
|
+
fallback_reason: fallbackReason,
|
|
1151
|
+
file_tree_hash: treeHash || "",
|
|
1152
|
+
branch_hash: bh || ""
|
|
1153
|
+
};
|
|
1154
|
+
return out;
|
|
1155
|
+
}
|
|
1156
|
+
function readFrontmatterType(path) {
|
|
1157
|
+
if (!existsSync2(path)) return null;
|
|
1158
|
+
let src;
|
|
1159
|
+
try {
|
|
1160
|
+
src = readFileSync3(path, "utf8");
|
|
1161
|
+
} catch {
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
if (!src.startsWith("---\n")) return null;
|
|
1165
|
+
const after = src.slice(4);
|
|
1166
|
+
const closeIdx = after.search(/\n---\s*(?:\r?\n|$)/);
|
|
1167
|
+
if (closeIdx < 0) return null;
|
|
1168
|
+
const fm = after.slice(0, closeIdx);
|
|
1169
|
+
for (const ln of fm.split(/\r?\n/)) {
|
|
1170
|
+
const m2 = ln.match(/^type\s*:\s*(\S+)\s*$/);
|
|
1171
|
+
if (m2) {
|
|
1172
|
+
const v2 = m2[1].replace(/^["']|["']$/g, "");
|
|
1173
|
+
return v2;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
function walkProject(root, { maxFiles, maxDepth, options, timeBudgetMs }) {
|
|
1179
|
+
const out = {
|
|
1180
|
+
filesScanned: 0,
|
|
1181
|
+
totalEstimate: 0,
|
|
1182
|
+
incomplete: false,
|
|
1183
|
+
lastPathWalked: "",
|
|
1184
|
+
fingerprint: [],
|
|
1185
|
+
manifestsFound: [],
|
|
1186
|
+
dirHits: { book: [], content: [], business: [], design: [] },
|
|
1187
|
+
extTotals: {},
|
|
1188
|
+
patternHits: []
|
|
1189
|
+
};
|
|
1190
|
+
let resumeFrom = null;
|
|
1191
|
+
let priorState = null;
|
|
1192
|
+
if (options.resume !== false) {
|
|
1193
|
+
const state = loadScanState(root);
|
|
1194
|
+
if (state && shouldResume(state)) {
|
|
1195
|
+
resumeFrom = state.last_path_walked || null;
|
|
1196
|
+
priorState = state;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
if (priorState && priorState.partial && typeof priorState.partial === "object") {
|
|
1200
|
+
const p = priorState.partial;
|
|
1201
|
+
out.filesScanned = Number.isFinite(p.files_scanned) ? p.files_scanned : 0;
|
|
1202
|
+
out.totalEstimate = Number.isFinite(p.total_estimate) ? p.total_estimate : out.filesScanned;
|
|
1203
|
+
if (Array.isArray(p.fingerprint)) out.fingerprint = p.fingerprint.slice(0, 4096);
|
|
1204
|
+
if (Array.isArray(p.manifestsFound)) out.manifestsFound = p.manifestsFound.slice();
|
|
1205
|
+
if (p.dirHits && typeof p.dirHits === "object") {
|
|
1206
|
+
for (const k2 of ["book", "content", "business", "design"]) {
|
|
1207
|
+
if (Array.isArray(p.dirHits[k2])) out.dirHits[k2] = p.dirHits[k2].slice();
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
if (p.extTotals && typeof p.extTotals === "object") out.extTotals = { ...p.extTotals };
|
|
1211
|
+
if (Array.isArray(p.patternHits)) out.patternHits = p.patternHits.slice();
|
|
1212
|
+
}
|
|
1213
|
+
let resumed = !resumeFrom;
|
|
1214
|
+
const visitedDirs = /* @__PURE__ */ new Set();
|
|
1215
|
+
try {
|
|
1216
|
+
visitedDirs.add(realpathSync.native(root));
|
|
1217
|
+
} catch {
|
|
1218
|
+
}
|
|
1219
|
+
const startedAt = Date.now();
|
|
1220
|
+
const budget = Number.isFinite(timeBudgetMs) && timeBudgetMs > 0 ? timeBudgetMs : DEFAULT_TIME_BUDGET_MS;
|
|
1221
|
+
let entriesSinceTimeCheck = 0;
|
|
1222
|
+
const stack = [{ path: root, depth: 0 }];
|
|
1223
|
+
while (stack.length > 0) {
|
|
1224
|
+
const { path, depth } = stack.pop();
|
|
1225
|
+
if (depth > maxDepth) continue;
|
|
1226
|
+
let entries;
|
|
1227
|
+
try {
|
|
1228
|
+
entries = readdirSync3(path, { withFileTypes: true });
|
|
1229
|
+
} catch {
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
entries.sort((a, b2) => a.name < b2.name ? -1 : a.name > b2.name ? 1 : 0);
|
|
1233
|
+
for (const entry of entries) {
|
|
1234
|
+
const childPath = join5(path, entry.name);
|
|
1235
|
+
if (!resumed) {
|
|
1236
|
+
if (childPath === resumeFrom) resumed = true;
|
|
1237
|
+
continue;
|
|
1238
|
+
}
|
|
1239
|
+
out.lastPathWalked = childPath;
|
|
1240
|
+
entriesSinceTimeCheck += 1;
|
|
1241
|
+
if (entriesSinceTimeCheck >= TIME_BUDGET_CHECK_EVERY) {
|
|
1242
|
+
entriesSinceTimeCheck = 0;
|
|
1243
|
+
if (Date.now() - startedAt > budget) {
|
|
1244
|
+
out.incomplete = true;
|
|
1245
|
+
return out;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
if (entry.isDirectory()) {
|
|
1249
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1250
|
+
try {
|
|
1251
|
+
const real = realpathSync.native(childPath);
|
|
1252
|
+
if (visitedDirs.has(real)) continue;
|
|
1253
|
+
visitedDirs.add(real);
|
|
1254
|
+
} catch {
|
|
1255
|
+
}
|
|
1256
|
+
recordDirHit(out, entry.name, depth);
|
|
1257
|
+
stack.push({ path: childPath, depth: depth + 1 });
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
if (!entry.isFile()) continue;
|
|
1261
|
+
out.filesScanned += 1;
|
|
1262
|
+
out.totalEstimate = Math.max(out.totalEstimate, out.filesScanned);
|
|
1263
|
+
if (out.fingerprint.length < 4096) {
|
|
1264
|
+
out.fingerprint.push(childPath.slice(root.length + 1));
|
|
1265
|
+
}
|
|
1266
|
+
if (depth <= 2 && SOFTWARE_MANIFESTS.includes(entry.name)) {
|
|
1267
|
+
out.manifestsFound.push(entry.name);
|
|
1268
|
+
}
|
|
1269
|
+
for (const p of FILENAME_PATTERNS) {
|
|
1270
|
+
if (p.re.test(entry.name)) {
|
|
1271
|
+
out.patternHits.push({ name: entry.name, domain: p.domain, weight: p.weight });
|
|
1272
|
+
break;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
const ext = extname(entry.name).toLowerCase();
|
|
1276
|
+
const dom = EXT_DOMAIN[ext];
|
|
1277
|
+
if (dom) {
|
|
1278
|
+
out.extTotals[dom] = (out.extTotals[dom] || 0) + 1;
|
|
1279
|
+
}
|
|
1280
|
+
if (out.filesScanned % CHECKPOINT_EVERY === 0) {
|
|
1281
|
+
try {
|
|
1282
|
+
writeScanState(root, {
|
|
1283
|
+
scan_id: newScanId(),
|
|
1284
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1285
|
+
last_path_walked: childPath,
|
|
1286
|
+
files_scanned: out.filesScanned,
|
|
1287
|
+
total_estimate: out.totalEstimate,
|
|
1288
|
+
attempts: 1,
|
|
1289
|
+
incomplete: true,
|
|
1290
|
+
partial: snapshotPartial(out)
|
|
1291
|
+
});
|
|
1292
|
+
} catch {
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (out.filesScanned >= maxFiles) {
|
|
1296
|
+
out.incomplete = true;
|
|
1297
|
+
return out;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return out;
|
|
1302
|
+
}
|
|
1303
|
+
function snapshotPartial(out) {
|
|
1304
|
+
return {
|
|
1305
|
+
files_scanned: out.filesScanned,
|
|
1306
|
+
total_estimate: out.totalEstimate,
|
|
1307
|
+
fingerprint: out.fingerprint.slice(0, 4096),
|
|
1308
|
+
manifestsFound: out.manifestsFound.slice(0, 32),
|
|
1309
|
+
dirHits: {
|
|
1310
|
+
book: out.dirHits.book.slice(0, 32),
|
|
1311
|
+
content: out.dirHits.content.slice(0, 32),
|
|
1312
|
+
business: out.dirHits.business.slice(0, 32),
|
|
1313
|
+
design: out.dirHits.design.slice(0, 32)
|
|
1314
|
+
},
|
|
1315
|
+
extTotals: { ...out.extTotals },
|
|
1316
|
+
patternHits: out.patternHits.slice(0, 64)
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
function resolveTimeBudgetMs(options) {
|
|
1320
|
+
if (Number.isFinite(options.timeBudgetMs) && options.timeBudgetMs > 0) {
|
|
1321
|
+
return options.timeBudgetMs;
|
|
1322
|
+
}
|
|
1323
|
+
const env = process.env.IJFW_DETECT_TIME_BUDGET_MS;
|
|
1324
|
+
if (env) {
|
|
1325
|
+
const n = Number(env);
|
|
1326
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
1327
|
+
}
|
|
1328
|
+
return DEFAULT_TIME_BUDGET_MS;
|
|
1329
|
+
}
|
|
1330
|
+
function recordDirHit(out, name12, depth) {
|
|
1331
|
+
if (depth > 2) return;
|
|
1332
|
+
const lower = name12.toLowerCase();
|
|
1333
|
+
if (BOOK_DIRS.includes(lower)) out.dirHits.book.push(lower);
|
|
1334
|
+
if (CONTENT_DIRS.includes(lower)) out.dirHits.content.push(lower);
|
|
1335
|
+
if (BUSINESS_DIRS.includes(lower)) out.dirHits.business.push(lower);
|
|
1336
|
+
if (DESIGN_DIRS.includes(lower)) out.dirHits.design.push(lower);
|
|
1337
|
+
}
|
|
1338
|
+
function scoreSignals(signals) {
|
|
1339
|
+
const board = {
|
|
1340
|
+
software: 0,
|
|
1341
|
+
book: 0,
|
|
1342
|
+
content: 0,
|
|
1343
|
+
business: 0,
|
|
1344
|
+
design: 0,
|
|
1345
|
+
mixed: 0,
|
|
1346
|
+
unknown: 0
|
|
1347
|
+
};
|
|
1348
|
+
const patternBudget = { software: 0.8, book: 0.8, content: 0.8, business: 0.8, design: 0.8, mixed: 0.8, unknown: 0.8 };
|
|
1349
|
+
const dirBudget = { software: 0.6, book: 0.6, content: 0.6, business: 0.6, design: 0.6, mixed: 0.6, unknown: 0.6 };
|
|
1350
|
+
for (const s of signals) {
|
|
1351
|
+
if (s.kind === "user_declaration" && s.value) board[s.value] += 1;
|
|
1352
|
+
else if (s.kind === "agents_md_frontmatter" && s.value) board[s.value] += 0.9;
|
|
1353
|
+
else if (s.kind === "brief_md_frontmatter" && s.value) board[s.value] += 0.8;
|
|
1354
|
+
else if (s.kind === "manifest") board.software += 0.9;
|
|
1355
|
+
else if (s.kind === "dir_book") {
|
|
1356
|
+
const add = Math.min(0.4, dirBudget.book);
|
|
1357
|
+
board.book += add;
|
|
1358
|
+
dirBudget.book -= add;
|
|
1359
|
+
} else if (s.kind === "dir_content") {
|
|
1360
|
+
const add = Math.min(0.4, dirBudget.content);
|
|
1361
|
+
board.content += add;
|
|
1362
|
+
dirBudget.content -= add;
|
|
1363
|
+
} else if (s.kind === "dir_business") {
|
|
1364
|
+
const add = Math.min(0.4, dirBudget.business);
|
|
1365
|
+
board.business += add;
|
|
1366
|
+
dirBudget.business -= add;
|
|
1367
|
+
} else if (s.kind === "dir_design") {
|
|
1368
|
+
const add = Math.min(0.4, dirBudget.design);
|
|
1369
|
+
board.design += add;
|
|
1370
|
+
dirBudget.design -= add;
|
|
1371
|
+
} else if (s.kind === "file_extension_ratio") {
|
|
1372
|
+
const m2 = s.ratio || 0;
|
|
1373
|
+
board[s.domain] = (board[s.domain] || 0) + 0.7 * m2;
|
|
1374
|
+
} else if (s.kind === "filename_pattern") {
|
|
1375
|
+
const add = Math.min(s.weight, patternBudget[s.domain] || 0);
|
|
1376
|
+
if (add > 0) {
|
|
1377
|
+
board[s.domain] = (board[s.domain] || 0) + add;
|
|
1378
|
+
patternBudget[s.domain] -= add;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return board;
|
|
1383
|
+
}
|
|
1384
|
+
function rankDomains(board) {
|
|
1385
|
+
const arr = Object.entries(board).filter(([d]) => d !== "mixed" && d !== "unknown").map(([domain, raw]) => ({ domain, raw }));
|
|
1386
|
+
if (arr.length === 0) return [];
|
|
1387
|
+
const maxRaw = arr.reduce((m2, e) => Math.max(m2, e.raw), 0);
|
|
1388
|
+
if (maxRaw <= 0) return [];
|
|
1389
|
+
for (const e of arr) {
|
|
1390
|
+
e.score = anchor(e.raw, maxRaw);
|
|
1391
|
+
}
|
|
1392
|
+
arr.sort((a, b2) => b2.score - a.score);
|
|
1393
|
+
return arr;
|
|
1394
|
+
}
|
|
1395
|
+
function anchor(raw, maxRaw) {
|
|
1396
|
+
if (raw <= 0) return 0;
|
|
1397
|
+
const top = Math.min(0.95, 0.4 + 0.55 * Math.tanh(raw));
|
|
1398
|
+
return Number((top * (raw / maxRaw)).toFixed(3));
|
|
1399
|
+
}
|
|
1400
|
+
function fileTreeHash(paths) {
|
|
1401
|
+
if (!paths || paths.length === 0) return "";
|
|
1402
|
+
const h = createHash("sha256");
|
|
1403
|
+
for (const p of paths) h.update(p + "\n");
|
|
1404
|
+
return h.digest("hex").slice(0, 16);
|
|
1405
|
+
}
|
|
1406
|
+
function branchHash(root) {
|
|
1407
|
+
try {
|
|
1408
|
+
const dotGit = join5(root, ".git");
|
|
1409
|
+
if (!existsSync2(dotGit)) return "";
|
|
1410
|
+
let headPath = null;
|
|
1411
|
+
let st;
|
|
1412
|
+
try {
|
|
1413
|
+
st = statSync3(dotGit);
|
|
1414
|
+
} catch {
|
|
1415
|
+
return "";
|
|
1416
|
+
}
|
|
1417
|
+
if (st.isDirectory()) {
|
|
1418
|
+
headPath = join5(dotGit, "HEAD");
|
|
1419
|
+
} else if (st.isFile()) {
|
|
1420
|
+
const ptr = readFileSync3(dotGit, "utf8");
|
|
1421
|
+
const m3 = ptr.match(/^gitdir:\s*(.+?)\s*$/m);
|
|
1422
|
+
if (!m3) return "";
|
|
1423
|
+
const target = m3[1];
|
|
1424
|
+
const gitDir = isAbsolute(target) ? target : pathResolve(root, target);
|
|
1425
|
+
headPath = join5(gitDir, "HEAD");
|
|
1426
|
+
} else {
|
|
1427
|
+
return "";
|
|
1428
|
+
}
|
|
1429
|
+
if (!headPath || !existsSync2(headPath)) return "";
|
|
1430
|
+
const head = readFileSync3(headPath, "utf8").trim();
|
|
1431
|
+
const m2 = head.match(/^ref:\s*(.+)$/);
|
|
1432
|
+
const branch = m2 ? m2[1] : head;
|
|
1433
|
+
return createHash("sha256").update(branch).digest("hex").slice(0, 16);
|
|
1434
|
+
} catch {
|
|
1435
|
+
}
|
|
1436
|
+
return "";
|
|
1437
|
+
}
|
|
1438
|
+
function isC9AvailableSync() {
|
|
1439
|
+
if (_c9AvailableCache !== null) return _c9AvailableCache;
|
|
1440
|
+
try {
|
|
1441
|
+
const here = fileURLToPath(import.meta.url);
|
|
1442
|
+
const fts5Path = join5(dirname(here), "compute", "fts5.js");
|
|
1443
|
+
_c9AvailableCache = existsSync2(fts5Path);
|
|
1444
|
+
} catch {
|
|
1445
|
+
_c9AvailableCache = false;
|
|
1446
|
+
}
|
|
1447
|
+
return _c9AvailableCache;
|
|
1448
|
+
}
|
|
1449
|
+
function newScanId() {
|
|
1450
|
+
return createHash("sha256").update(String(process.pid) + ":" + String(Date.now()) + ":" + Math.random()).digest("hex").slice(0, 12);
|
|
1451
|
+
}
|
|
1452
|
+
var DOMAINS, MAX_FILES, MAX_DEPTH, CHECKPOINT_EVERY, DEFAULT_TIME_BUDGET_MS, TIME_BUDGET_CHECK_EVERY, SKIP_DIRS, EXT_DOMAIN, SOFTWARE_MANIFESTS, BOOK_DIRS, CONTENT_DIRS, BUSINESS_DIRS, DESIGN_DIRS, FILENAME_PATTERNS, _c9AvailableCache;
|
|
1453
|
+
var init_project_type_detector = __esm({
|
|
1454
|
+
"../mcp-server/src/project-type-detector.js"() {
|
|
1455
|
+
init_scan_resume();
|
|
1456
|
+
DOMAINS = ["software", "book", "content", "business", "design", "mixed", "unknown"];
|
|
1457
|
+
MAX_FILES = 2e5;
|
|
1458
|
+
MAX_DEPTH = 12;
|
|
1459
|
+
CHECKPOINT_EVERY = 500;
|
|
1460
|
+
DEFAULT_TIME_BUDGET_MS = 5e3;
|
|
1461
|
+
TIME_BUDGET_CHECK_EVERY = 1e3;
|
|
1462
|
+
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1463
|
+
".git",
|
|
1464
|
+
".hg",
|
|
1465
|
+
".svn",
|
|
1466
|
+
".ijfw",
|
|
1467
|
+
".planning",
|
|
1468
|
+
".cache",
|
|
1469
|
+
"node_modules",
|
|
1470
|
+
"dist",
|
|
1471
|
+
"build",
|
|
1472
|
+
"out",
|
|
1473
|
+
"target",
|
|
1474
|
+
".next",
|
|
1475
|
+
"__pycache__",
|
|
1476
|
+
".venv",
|
|
1477
|
+
"venv",
|
|
1478
|
+
"env",
|
|
1479
|
+
".pytest_cache",
|
|
1480
|
+
".mypy_cache",
|
|
1481
|
+
".tox",
|
|
1482
|
+
".gradle",
|
|
1483
|
+
".idea",
|
|
1484
|
+
".vscode",
|
|
1485
|
+
"vendor",
|
|
1486
|
+
"bower_components"
|
|
1487
|
+
]);
|
|
1488
|
+
EXT_DOMAIN = {
|
|
1489
|
+
// software (heavy)
|
|
1490
|
+
".js": "software",
|
|
1491
|
+
".jsx": "software",
|
|
1492
|
+
".ts": "software",
|
|
1493
|
+
".tsx": "software",
|
|
1494
|
+
".mjs": "software",
|
|
1495
|
+
".cjs": "software",
|
|
1496
|
+
".py": "software",
|
|
1497
|
+
".rs": "software",
|
|
1498
|
+
".go": "software",
|
|
1499
|
+
".java": "software",
|
|
1500
|
+
".kt": "software",
|
|
1501
|
+
".scala": "software",
|
|
1502
|
+
".rb": "software",
|
|
1503
|
+
".php": "software",
|
|
1504
|
+
".c": "software",
|
|
1505
|
+
".cc": "software",
|
|
1506
|
+
".cpp": "software",
|
|
1507
|
+
".h": "software",
|
|
1508
|
+
".hpp": "software",
|
|
1509
|
+
".hh": "software",
|
|
1510
|
+
".swift": "software",
|
|
1511
|
+
".m": "software",
|
|
1512
|
+
".mm": "software",
|
|
1513
|
+
".cs": "software",
|
|
1514
|
+
".fs": "software",
|
|
1515
|
+
".lua": "software",
|
|
1516
|
+
".dart": "software",
|
|
1517
|
+
".zig": "software",
|
|
1518
|
+
// book / long-form prose
|
|
1519
|
+
".tex": "book",
|
|
1520
|
+
".bib": "book",
|
|
1521
|
+
".latex": "book",
|
|
1522
|
+
".epub": "book",
|
|
1523
|
+
".mobi": "book",
|
|
1524
|
+
// content / blog / docs / marketing
|
|
1525
|
+
".mdx": "content",
|
|
1526
|
+
".markdown": "content",
|
|
1527
|
+
".rst": "content",
|
|
1528
|
+
// design / assets
|
|
1529
|
+
".fig": "design",
|
|
1530
|
+
".sketch": "design",
|
|
1531
|
+
".xd": "design",
|
|
1532
|
+
".ai": "design",
|
|
1533
|
+
".psd": "design",
|
|
1534
|
+
".indd": "design",
|
|
1535
|
+
".svg": "design",
|
|
1536
|
+
".afdesign": "design",
|
|
1537
|
+
".afphoto": "design",
|
|
1538
|
+
// business / ops
|
|
1539
|
+
".xlsx": "business",
|
|
1540
|
+
".xls": "business",
|
|
1541
|
+
".csv": "business",
|
|
1542
|
+
".numbers": "business",
|
|
1543
|
+
".ods": "business",
|
|
1544
|
+
".pptx": "business",
|
|
1545
|
+
".ppt": "business",
|
|
1546
|
+
".key": "business",
|
|
1547
|
+
".docx": "business",
|
|
1548
|
+
".doc": "business"
|
|
1549
|
+
};
|
|
1550
|
+
SOFTWARE_MANIFESTS = [
|
|
1551
|
+
"package.json",
|
|
1552
|
+
"Cargo.toml",
|
|
1553
|
+
"pyproject.toml",
|
|
1554
|
+
"setup.py",
|
|
1555
|
+
"Gemfile",
|
|
1556
|
+
"go.mod",
|
|
1557
|
+
"pom.xml",
|
|
1558
|
+
"build.gradle",
|
|
1559
|
+
"build.gradle.kts",
|
|
1560
|
+
"composer.json",
|
|
1561
|
+
"Package.swift",
|
|
1562
|
+
"mix.exs",
|
|
1563
|
+
"rebar.config",
|
|
1564
|
+
"pubspec.yaml",
|
|
1565
|
+
"CMakeLists.txt",
|
|
1566
|
+
"Makefile"
|
|
1567
|
+
];
|
|
1568
|
+
BOOK_DIRS = ["manuscripts", "manuscript", "drafts", "draft", "chapters", "book"];
|
|
1569
|
+
CONTENT_DIRS = ["content", "posts", "articles", "blog", "newsletter", "social"];
|
|
1570
|
+
BUSINESS_DIRS = ["strategy", "financials", "finance", "ops", "runbooks", "sop", "sops", "ops-runbooks"];
|
|
1571
|
+
DESIGN_DIRS = ["designs", "design", "assets", "mockups", "wireframes", "figma"];
|
|
1572
|
+
FILENAME_PATTERNS = [
|
|
1573
|
+
{ re: /^chapter[-_]?\d+/i, domain: "book", weight: 0.4 },
|
|
1574
|
+
{ re: /^ch\d+/i, domain: "book", weight: 0.3 },
|
|
1575
|
+
{ re: /^brand[-_]voice/i, domain: "content", weight: 0.4 },
|
|
1576
|
+
{ re: /^seo[-_]/i, domain: "content", weight: 0.2 },
|
|
1577
|
+
{ re: /^post[-_]/i, domain: "content", weight: 0.2 },
|
|
1578
|
+
{ re: /^figma[-_]export/i, domain: "design", weight: 0.4 },
|
|
1579
|
+
{ re: /^wireframe/i, domain: "design", weight: 0.3 }
|
|
1580
|
+
];
|
|
1581
|
+
_c9AvailableCache = null;
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
1584
|
+
|
|
1585
|
+
// ../mcp-server/src/gate-result.js
|
|
1586
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
1587
|
+
import { basename, dirname as dirname2, join as join6 } from "node:path";
|
|
1588
|
+
async function emitGateResult(gateOpts, context = {}) {
|
|
1589
|
+
if (gateOpts === null || typeof gateOpts !== "object") {
|
|
1590
|
+
throw new TypeError("emitGateResult: gateOpts must be an object");
|
|
1591
|
+
}
|
|
1592
|
+
const projectType = typeof context.project_type === "string" && context.project_type.length > 0 ? context.project_type : await resolveProjectType(context.projectRoot);
|
|
1593
|
+
const result = {
|
|
1594
|
+
schema_version: SCHEMA_VERSION,
|
|
1595
|
+
gate_id: makeGateId(gateOpts.gate),
|
|
1596
|
+
gate: gateOpts.gate,
|
|
1597
|
+
status: gateOpts.status,
|
|
1598
|
+
project_type: projectType,
|
|
1599
|
+
lenses: Array.isArray(gateOpts.lenses) ? gateOpts.lenses : [],
|
|
1600
|
+
affected_artifacts: Array.isArray(gateOpts.affected_artifacts) ? gateOpts.affected_artifacts : [],
|
|
1601
|
+
accounting: gateOpts.accounting,
|
|
1602
|
+
remediation: Array.isArray(gateOpts.remediation) ? gateOpts.remediation : [],
|
|
1603
|
+
receipts_ref: gateOpts.receipts_ref === void 0 ? null : gateOpts.receipts_ref,
|
|
1604
|
+
supersedes: gateOpts.supersedes === void 0 ? null : gateOpts.supersedes,
|
|
1605
|
+
emitted_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1606
|
+
};
|
|
1607
|
+
const { valid, errors } = validateGateResult(result);
|
|
1608
|
+
if (!valid) {
|
|
1609
|
+
throw new Error(
|
|
1610
|
+
`emitGateResult: invalid gate-result \u2014 ${errors.join("; ")}`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
makeReceipt(result, {
|
|
1614
|
+
projectRoot: typeof context.projectRoot === "string" && context.projectRoot.length > 0 ? context.projectRoot : process.cwd()
|
|
1615
|
+
}).catch(() => {
|
|
1616
|
+
});
|
|
1617
|
+
return formatGateResult(result);
|
|
1618
|
+
}
|
|
1619
|
+
async function makeReceipt(gateResult, opts = {}) {
|
|
1620
|
+
try {
|
|
1621
|
+
if (!gateResult || typeof gateResult !== "object") return;
|
|
1622
|
+
const gateId = typeof gateResult.gate_id === "string" ? gateResult.gate_id : null;
|
|
1623
|
+
if (!gateId) return;
|
|
1624
|
+
if (!RECEIPT_GATE_ID_PATTERN.test(gateId)) {
|
|
1625
|
+
try {
|
|
1626
|
+
process.stderr.write(
|
|
1627
|
+
`ijfw: makeReceipt rejected unsafe gate_id "${gateId}"
|
|
1628
|
+
`
|
|
1629
|
+
);
|
|
1630
|
+
} catch {
|
|
1631
|
+
}
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
const safeId = basename(gateId);
|
|
1635
|
+
const root = typeof opts.projectRoot === "string" && opts.projectRoot.length > 0 ? opts.projectRoot : process.cwd();
|
|
1636
|
+
const receiptPath = join6(
|
|
1637
|
+
root,
|
|
1638
|
+
".ijfw",
|
|
1639
|
+
"memory",
|
|
1640
|
+
"gate-receipts",
|
|
1641
|
+
`${safeId}.json`
|
|
1642
|
+
);
|
|
1643
|
+
await mkdir(dirname2(receiptPath), { recursive: true });
|
|
1644
|
+
const body = JSON.stringify(gateResult, null, 2) + "\n";
|
|
1645
|
+
await writeFile(receiptPath, body, "utf8");
|
|
1646
|
+
} catch (err) {
|
|
1647
|
+
const msg = err && err.message ? err.message : String(err);
|
|
1648
|
+
try {
|
|
1649
|
+
process.stderr.write(`ijfw: gate-result receipt write failed: ${msg}
|
|
1650
|
+
`);
|
|
1651
|
+
} catch {
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
async function resolveProjectType(projectRoot) {
|
|
1656
|
+
try {
|
|
1657
|
+
const root = typeof projectRoot === "string" && projectRoot.length > 0 ? projectRoot : process.cwd();
|
|
1658
|
+
const detected = detect(root);
|
|
1659
|
+
if (detected && typeof detected.primary_type === "string" && detected.primary_type.length > 0) {
|
|
1660
|
+
return detected.primary_type;
|
|
1661
|
+
}
|
|
1662
|
+
if (detected && typeof detected.type === "string" && detected.type.length > 0) {
|
|
1663
|
+
return detected.type;
|
|
1664
|
+
}
|
|
1665
|
+
return "unknown";
|
|
1666
|
+
} catch {
|
|
1667
|
+
return "unknown";
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
var RECEIPT_GATE_ID_PATTERN;
|
|
1671
|
+
var init_gate_result = __esm({
|
|
1672
|
+
"../mcp-server/src/gate-result.js"() {
|
|
1673
|
+
init_gate_result_schema();
|
|
1674
|
+
init_project_type_detector();
|
|
1675
|
+
RECEIPT_GATE_ID_PATTERN = /^[a-z][a-z0-9-]+$/;
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
|
|
689
1679
|
// src/preflight/gates/audit-ci.js
|
|
690
1680
|
var audit_ci_exports = {};
|
|
691
1681
|
__export(audit_ci_exports, {
|
|
@@ -695,7 +1685,7 @@ __export(audit_ci_exports, {
|
|
|
695
1685
|
severity: () => severity7
|
|
696
1686
|
});
|
|
697
1687
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
698
|
-
import { join as
|
|
1688
|
+
import { join as join7 } from "node:path";
|
|
699
1689
|
function parseAuditReport(output) {
|
|
700
1690
|
const start = output.indexOf("{");
|
|
701
1691
|
if (start === -1) return null;
|
|
@@ -726,7 +1716,7 @@ async function run7(ctx) {
|
|
|
726
1716
|
["audit", "--audit-level=high", "--json"],
|
|
727
1717
|
{
|
|
728
1718
|
encoding: "utf8",
|
|
729
|
-
cwd:
|
|
1719
|
+
cwd: join7(ctx.repoRoot, dir),
|
|
730
1720
|
timeout: 6e4
|
|
731
1721
|
}
|
|
732
1722
|
);
|
|
@@ -737,36 +1727,63 @@ async function run7(ctx) {
|
|
|
737
1727
|
});
|
|
738
1728
|
const durationMs = Date.now() - t0;
|
|
739
1729
|
const failed = runs.filter((r) => !r.report || r.highCritical > 0);
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1730
|
+
const status = failed.length === 0 ? "PASS" : "FAIL";
|
|
1731
|
+
const message = status === "PASS" ? "audit-ci: no high/critical vulnerabilities in installer or mcp-server" : "audit-ci: high or critical vulnerabilities found";
|
|
1732
|
+
let details;
|
|
1733
|
+
if (status === "PASS") {
|
|
1734
|
+
details = runs.map((r) => `${r.dir}: pass`);
|
|
1735
|
+
} else {
|
|
1736
|
+
const lines = [];
|
|
1737
|
+
for (const r of failed) {
|
|
1738
|
+
if (!r.report) {
|
|
1739
|
+
lines.push(`${r.dir}: audit report unavailable`);
|
|
1740
|
+
lines.push(...r.output.split("\n").filter(Boolean).slice(0, 10));
|
|
1741
|
+
continue;
|
|
1742
|
+
}
|
|
1743
|
+
lines.push(`${r.dir}: ${r.highCritical} high/critical advisory item(s)`);
|
|
1744
|
+
lines.push(...vulnerableNames(r.report).slice(0, 10));
|
|
1745
|
+
}
|
|
1746
|
+
details = lines.slice(0, 20);
|
|
748
1747
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1748
|
+
try {
|
|
1749
|
+
const block = await emitGateResult(
|
|
1750
|
+
{
|
|
1751
|
+
gate: "preflight:audit-ci",
|
|
1752
|
+
status,
|
|
1753
|
+
lenses: [],
|
|
1754
|
+
affected_artifacts: [],
|
|
1755
|
+
accounting: {
|
|
1756
|
+
duration_ms: durationMs,
|
|
1757
|
+
lenses_invoked: 0,
|
|
1758
|
+
cost_usd: null
|
|
1759
|
+
},
|
|
1760
|
+
remediation: []
|
|
1761
|
+
},
|
|
1762
|
+
ctx && ctx.repoRoot ? { projectRoot: ctx.repoRoot } : {}
|
|
1763
|
+
);
|
|
1764
|
+
if (typeof block === "string" && block.length > 0) {
|
|
1765
|
+
details = [...details, block];
|
|
1766
|
+
}
|
|
1767
|
+
} catch (err) {
|
|
1768
|
+
const msg = err && err.message ? err.message : String(err);
|
|
1769
|
+
try {
|
|
1770
|
+
process.stderr.write(`ijfw: preflight:audit-ci gate-result emit failed: ${msg}
|
|
1771
|
+
`);
|
|
1772
|
+
} catch {
|
|
755
1773
|
}
|
|
756
|
-
lines.push(`${r.dir}: ${r.highCritical} high/critical advisory item(s)`);
|
|
757
|
-
lines.push(...vulnerableNames(r.report).slice(0, 10));
|
|
758
1774
|
}
|
|
759
1775
|
return {
|
|
760
1776
|
name: "audit-ci",
|
|
761
|
-
status
|
|
762
|
-
message
|
|
763
|
-
details
|
|
1777
|
+
status,
|
|
1778
|
+
message,
|
|
1779
|
+
details,
|
|
764
1780
|
durationMs
|
|
765
1781
|
};
|
|
766
1782
|
}
|
|
767
1783
|
var name7, severity7, parallel7;
|
|
768
1784
|
var init_audit_ci = __esm({
|
|
769
1785
|
"src/preflight/gates/audit-ci.js"() {
|
|
1786
|
+
init_gate_result();
|
|
770
1787
|
name7 = "audit-ci";
|
|
771
1788
|
severity7 = "blocking";
|
|
772
1789
|
parallel7 = true;
|
|
@@ -828,7 +1845,7 @@ __export(license_check_exports, {
|
|
|
828
1845
|
severity: () => severity9
|
|
829
1846
|
});
|
|
830
1847
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
831
|
-
import { join as
|
|
1848
|
+
import { join as join8 } from "node:path";
|
|
832
1849
|
async function run9(ctx) {
|
|
833
1850
|
const t0 = Date.now();
|
|
834
1851
|
const ver = ctx.versions["license-checker"] || "latest";
|
|
@@ -837,7 +1854,7 @@ async function run9(ctx) {
|
|
|
837
1854
|
["--yes", `license-checker@${ver}`, "--onlyAllow", ALLOWED, "--production"],
|
|
838
1855
|
{
|
|
839
1856
|
encoding: "utf8",
|
|
840
|
-
cwd:
|
|
1857
|
+
cwd: join8(ctx.repoRoot, "installer"),
|
|
841
1858
|
timeout: 3e4
|
|
842
1859
|
}
|
|
843
1860
|
);
|
|
@@ -880,12 +1897,12 @@ __export(pack_smoke_exports, {
|
|
|
880
1897
|
severity: () => severity10
|
|
881
1898
|
});
|
|
882
1899
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
883
|
-
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, mkdirSync, writeFileSync as
|
|
884
|
-
import { join as
|
|
1900
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readdirSync as readdirSync4, existsSync as existsSync3 } from "node:fs";
|
|
1901
|
+
import { join as join9, resolve } from "node:path";
|
|
885
1902
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
886
1903
|
async function run10(ctx) {
|
|
887
1904
|
const t0 = Date.now();
|
|
888
|
-
const installerDir =
|
|
1905
|
+
const installerDir = join9(ctx.repoRoot, "installer");
|
|
889
1906
|
const build = spawnSync10("npm", ["run", "build"], {
|
|
890
1907
|
encoding: "utf8",
|
|
891
1908
|
cwd: installerDir,
|
|
@@ -927,13 +1944,13 @@ async function run10(ctx) {
|
|
|
927
1944
|
};
|
|
928
1945
|
}
|
|
929
1946
|
const tarballPath = resolve(installerDir, tarball);
|
|
930
|
-
const tmpRoot = mkdtempSync2(
|
|
931
|
-
const fakeHome =
|
|
932
|
-
const installDir =
|
|
933
|
-
|
|
934
|
-
|
|
1947
|
+
const tmpRoot = mkdtempSync2(join9(tmpdir2(), "ijfw-pack-smoke-"));
|
|
1948
|
+
const fakeHome = join9(tmpRoot, "home");
|
|
1949
|
+
const installDir = join9(tmpRoot, "install");
|
|
1950
|
+
mkdirSync3(fakeHome, { recursive: true });
|
|
1951
|
+
mkdirSync3(installDir, { recursive: true });
|
|
935
1952
|
try {
|
|
936
|
-
|
|
1953
|
+
writeFileSync4(join9(installDir, "package.json"), JSON.stringify({ name: "smoke-test", version: "1.0.0", type: "module" }));
|
|
937
1954
|
const install = spawnSync10("npm", ["install", "--no-save", tarballPath], {
|
|
938
1955
|
encoding: "utf8",
|
|
939
1956
|
cwd: installDir,
|
|
@@ -951,25 +1968,25 @@ async function run10(ctx) {
|
|
|
951
1968
|
};
|
|
952
1969
|
}
|
|
953
1970
|
const binCandidates = [
|
|
954
|
-
|
|
955
|
-
|
|
1971
|
+
join9(installDir, "node_modules", ".bin", "ijfw"),
|
|
1972
|
+
join9(installDir, "node_modules", ".bin", "ijfw-install")
|
|
956
1973
|
];
|
|
957
1974
|
let binPath = null;
|
|
958
1975
|
for (const c2 of binCandidates) {
|
|
959
|
-
if (
|
|
1976
|
+
if (existsSync3(c2)) {
|
|
960
1977
|
binPath = c2;
|
|
961
1978
|
break;
|
|
962
1979
|
}
|
|
963
1980
|
}
|
|
964
1981
|
if (!binPath) {
|
|
965
|
-
const binDir =
|
|
1982
|
+
const binDir = join9(installDir, "node_modules", ".bin");
|
|
966
1983
|
let entries = [];
|
|
967
1984
|
try {
|
|
968
|
-
entries =
|
|
1985
|
+
entries = readdirSync4(binDir);
|
|
969
1986
|
} catch {
|
|
970
1987
|
}
|
|
971
1988
|
const found = entries.find((e) => e.startsWith("ijfw"));
|
|
972
|
-
if (found) binPath =
|
|
1989
|
+
if (found) binPath = join9(binDir, found);
|
|
973
1990
|
}
|
|
974
1991
|
if (!binPath) {
|
|
975
1992
|
return {
|
|
@@ -1032,12 +2049,12 @@ __export(upgrade_smoke_exports, {
|
|
|
1032
2049
|
severity: () => severity11
|
|
1033
2050
|
});
|
|
1034
2051
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
1035
|
-
import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, mkdirSync as
|
|
1036
|
-
import { join as
|
|
2052
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
|
|
2053
|
+
import { join as join10, resolve as resolve2 } from "node:path";
|
|
1037
2054
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
1038
2055
|
async function run11(ctx) {
|
|
1039
2056
|
const t0 = Date.now();
|
|
1040
|
-
const installerDir =
|
|
2057
|
+
const installerDir = join10(ctx.repoRoot, "installer");
|
|
1041
2058
|
const build = spawnSync11("npm", ["run", "build"], {
|
|
1042
2059
|
encoding: "utf8",
|
|
1043
2060
|
cwd: installerDir,
|
|
@@ -1070,15 +2087,15 @@ async function run11(ctx) {
|
|
|
1070
2087
|
}
|
|
1071
2088
|
const tarball = pack.stdout.trim();
|
|
1072
2089
|
const tarballPath = resolve2(installerDir, tarball);
|
|
1073
|
-
const tmpRoot = mkdtempSync3(
|
|
1074
|
-
const fakeHome =
|
|
1075
|
-
const installDir =
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
const claudeDir =
|
|
1079
|
-
|
|
2090
|
+
const tmpRoot = mkdtempSync3(join10(tmpdir3(), "ijfw-upgrade-smoke-"));
|
|
2091
|
+
const fakeHome = join10(tmpRoot, "home");
|
|
2092
|
+
const installDir = join10(tmpRoot, "install");
|
|
2093
|
+
mkdirSync4(fakeHome, { recursive: true });
|
|
2094
|
+
mkdirSync4(installDir, { recursive: true });
|
|
2095
|
+
const claudeDir = join10(fakeHome, ".claude");
|
|
2096
|
+
mkdirSync4(claudeDir, { recursive: true });
|
|
1080
2097
|
try {
|
|
1081
|
-
|
|
2098
|
+
writeFileSync5(join10(installDir, "package.json"), JSON.stringify({ name: "upgrade-smoke", version: "1.0.0", type: "module" }));
|
|
1082
2099
|
const install = spawnSync11("npm", ["install", "--no-save", tarballPath], {
|
|
1083
2100
|
encoding: "utf8",
|
|
1084
2101
|
cwd: installDir,
|
|
@@ -1096,12 +2113,12 @@ async function run11(ctx) {
|
|
|
1096
2113
|
};
|
|
1097
2114
|
}
|
|
1098
2115
|
const binCandidates = [
|
|
1099
|
-
|
|
1100
|
-
|
|
2116
|
+
join10(installDir, "node_modules", ".bin", "ijfw-install"),
|
|
2117
|
+
join10(installDir, "node_modules", ".bin", "ijfw")
|
|
1101
2118
|
];
|
|
1102
2119
|
let installerBin = null;
|
|
1103
2120
|
for (const c2 of binCandidates) {
|
|
1104
|
-
if (
|
|
2121
|
+
if (existsSync4(c2)) {
|
|
1105
2122
|
installerBin = c2;
|
|
1106
2123
|
break;
|
|
1107
2124
|
}
|
|
@@ -1115,11 +2132,11 @@ async function run11(ctx) {
|
|
|
1115
2132
|
durationMs: Date.now() - t0
|
|
1116
2133
|
};
|
|
1117
2134
|
}
|
|
1118
|
-
const settingsPath =
|
|
1119
|
-
if (
|
|
2135
|
+
const settingsPath = join10(claudeDir, "settings.json");
|
|
2136
|
+
if (existsSync4(settingsPath)) {
|
|
1120
2137
|
let settings;
|
|
1121
2138
|
try {
|
|
1122
|
-
settings = JSON.parse(
|
|
2139
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf8"));
|
|
1123
2140
|
} catch (e) {
|
|
1124
2141
|
return {
|
|
1125
2142
|
name: "upgrade-smoke",
|
|
@@ -1140,9 +2157,9 @@ async function run11(ctx) {
|
|
|
1140
2157
|
};
|
|
1141
2158
|
}
|
|
1142
2159
|
}
|
|
1143
|
-
const marketplaceSrc =
|
|
1144
|
-
if (
|
|
1145
|
-
const src =
|
|
2160
|
+
const marketplaceSrc = join10(installerDir, "src", "marketplace.js");
|
|
2161
|
+
if (existsSync4(marketplaceSrc)) {
|
|
2162
|
+
const src = readFileSync4(marketplaceSrc, "utf8");
|
|
1146
2163
|
const registersCorrectKey = src.includes("'ijfw@ijfw'") || src.includes('"ijfw@ijfw"');
|
|
1147
2164
|
const registersWrongKey = /enabledPlugins\[['"]ijfw-core@ijfw['"]\]\s*=\s*true/.test(src);
|
|
1148
2165
|
if (!registersCorrectKey) {
|
|
@@ -1197,9 +2214,9 @@ var preflight_exports = {};
|
|
|
1197
2214
|
__export(preflight_exports, {
|
|
1198
2215
|
runPreflightCommand: () => runPreflightCommand
|
|
1199
2216
|
});
|
|
1200
|
-
import { readFileSync as
|
|
1201
|
-
import { join as
|
|
1202
|
-
import { fileURLToPath } from "node:url";
|
|
2217
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "node:fs";
|
|
2218
|
+
import { join as join11, dirname as dirname3, resolve as resolve3 } from "node:path";
|
|
2219
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1203
2220
|
function printHelp() {
|
|
1204
2221
|
console.log(`
|
|
1205
2222
|
ijfw preflight -- 11-gate quality pipeline
|
|
@@ -1235,13 +2252,13 @@ SLO
|
|
|
1235
2252
|
}
|
|
1236
2253
|
function loadVersions(repoRoot2) {
|
|
1237
2254
|
const candidates = [
|
|
1238
|
-
|
|
1239
|
-
|
|
2255
|
+
join11(repoRoot2, "preflight-versions.json"),
|
|
2256
|
+
join11(repoRoot2, ".ijfw", "preflight-versions.json")
|
|
1240
2257
|
];
|
|
1241
2258
|
for (const f of candidates) {
|
|
1242
|
-
if (
|
|
2259
|
+
if (existsSync5(f)) {
|
|
1243
2260
|
try {
|
|
1244
|
-
return JSON.parse(
|
|
2261
|
+
return JSON.parse(readFileSync5(f, "utf8"));
|
|
1245
2262
|
} catch {
|
|
1246
2263
|
}
|
|
1247
2264
|
}
|
|
@@ -1300,7 +2317,7 @@ async function runPreflightCommand(argv, repoRoot2) {
|
|
|
1300
2317
|
function defaultRepoRoot() {
|
|
1301
2318
|
let dir = __dirname;
|
|
1302
2319
|
for (let i = 0; i < 8; i++) {
|
|
1303
|
-
if (
|
|
2320
|
+
if (existsSync5(join11(dir, "package.json")) && existsSync5(join11(dir, "mcp-server"))) return dir;
|
|
1304
2321
|
const next = resolve3(dir, "..");
|
|
1305
2322
|
if (next === dir) break;
|
|
1306
2323
|
dir = next;
|
|
@@ -1311,8 +2328,8 @@ var __dirname;
|
|
|
1311
2328
|
var init_preflight = __esm({
|
|
1312
2329
|
async "src/preflight.js"() {
|
|
1313
2330
|
init_runner();
|
|
1314
|
-
__dirname =
|
|
1315
|
-
if (process.argv[1] && resolve3(process.argv[1]) ===
|
|
2331
|
+
__dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
2332
|
+
if (process.argv[1] && resolve3(process.argv[1]) === fileURLToPath2(import.meta.url)) {
|
|
1316
2333
|
await runPreflightCommand(process.argv, defaultRepoRoot());
|
|
1317
2334
|
}
|
|
1318
2335
|
}
|
|
@@ -2578,30 +3595,30 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
2578
3595
|
});
|
|
2579
3596
|
|
|
2580
3597
|
// src/ijfw.js
|
|
2581
|
-
import { dirname as
|
|
2582
|
-
import { fileURLToPath as
|
|
2583
|
-
import { existsSync as
|
|
3598
|
+
import { dirname as dirname4, join as join12, resolve as resolve4, basename as basename2 } from "node:path";
|
|
3599
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3600
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, copyFileSync as copyFileSync3, readdirSync as readdirSync5, rmSync as rmSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2584
3601
|
import { homedir, platform } from "node:os";
|
|
2585
3602
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
2586
|
-
var __dirname2 =
|
|
3603
|
+
var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
|
|
2587
3604
|
function repoRoot() {
|
|
2588
3605
|
let dir = __dirname2;
|
|
2589
3606
|
for (let i = 0; i < 6; i++) {
|
|
2590
|
-
if (
|
|
3607
|
+
if (existsSync6(join12(dir, "package.json")) && existsSync6(join12(dir, ".git"))) return dir;
|
|
2591
3608
|
dir = resolve4(dir, "..");
|
|
2592
3609
|
}
|
|
2593
3610
|
return process.cwd();
|
|
2594
3611
|
}
|
|
2595
3612
|
function findInternalAsset(...rel) {
|
|
2596
3613
|
const root = repoRoot();
|
|
2597
|
-
const ijfwHome =
|
|
2598
|
-
const candidates = [
|
|
2599
|
-
return candidates.find((p) =>
|
|
3614
|
+
const ijfwHome = join12(homedir(), ".ijfw");
|
|
3615
|
+
const candidates = [join12(root, ...rel), join12(ijfwHome, ...rel)];
|
|
3616
|
+
return candidates.find((p) => existsSync6(p)) || null;
|
|
2600
3617
|
}
|
|
2601
3618
|
function readDashboardPort() {
|
|
2602
|
-
const portFile =
|
|
3619
|
+
const portFile = join12(homedir(), ".ijfw", "dashboard.port");
|
|
2603
3620
|
try {
|
|
2604
|
-
const port = Number.parseInt(
|
|
3621
|
+
const port = Number.parseInt(readFileSync6(portFile, "utf8").trim(), 10);
|
|
2605
3622
|
return Number.isFinite(port) ? port : 37891;
|
|
2606
3623
|
} catch {
|
|
2607
3624
|
return 37891;
|
|
@@ -2646,7 +3663,9 @@ var ORCHESTRATOR_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
2646
3663
|
"memory-consent",
|
|
2647
3664
|
"memory-why",
|
|
2648
3665
|
"metrics",
|
|
2649
|
-
"mode"
|
|
3666
|
+
"mode",
|
|
3667
|
+
"override",
|
|
3668
|
+
"extension"
|
|
2650
3669
|
]);
|
|
2651
3670
|
function printHelp2() {
|
|
2652
3671
|
console.log(`
|
|
@@ -2682,10 +3701,10 @@ function doctorCheck(cmd, args) {
|
|
|
2682
3701
|
}
|
|
2683
3702
|
function findCli() {
|
|
2684
3703
|
const candidates = [
|
|
2685
|
-
|
|
2686
|
-
|
|
3704
|
+
join12(repoRoot(), "mcp-server", "src", "cross-orchestrator-cli.js"),
|
|
3705
|
+
join12(homedir(), ".ijfw", "mcp-server", "src", "cross-orchestrator-cli.js")
|
|
2687
3706
|
];
|
|
2688
|
-
return candidates.find((p) =>
|
|
3707
|
+
return candidates.find((p) => existsSync6(p)) || null;
|
|
2689
3708
|
}
|
|
2690
3709
|
function delegateToCli(argTail) {
|
|
2691
3710
|
const cli = findCli();
|
|
@@ -2704,8 +3723,8 @@ async function main() {
|
|
|
2704
3723
|
const verbose = argv.slice(3).includes("--verbose");
|
|
2705
3724
|
if (delegateToCli(argv.slice(2))) return;
|
|
2706
3725
|
try {
|
|
2707
|
-
const pkgPath =
|
|
2708
|
-
const pkg = JSON.parse(
|
|
3726
|
+
const pkgPath = join12(__dirname2, "..", "package.json");
|
|
3727
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
2709
3728
|
console.log(`@ijfw/install@${pkg.version || "unknown"}`);
|
|
2710
3729
|
if (verbose) {
|
|
2711
3730
|
console.log(" (full --verbose details require a completed install: run ijfw install)");
|
|
@@ -2786,8 +3805,8 @@ async function main() {
|
|
|
2786
3805
|
console.error(`'ijfw design ${designSub}' requires a completed IJFW install. Run: ijfw install`);
|
|
2787
3806
|
process.exit(1);
|
|
2788
3807
|
}
|
|
2789
|
-
const contentDir =
|
|
2790
|
-
|
|
3808
|
+
const contentDir = join12(homedir(), ".ijfw", "design-companion", "content");
|
|
3809
|
+
mkdirSync5(contentDir, { recursive: true });
|
|
2791
3810
|
if (designSub === "start" || designSub === "open") {
|
|
2792
3811
|
const dashBin = findInternalAsset("mcp-server", "bin", "ijfw-dashboard");
|
|
2793
3812
|
if (!dashBin) {
|
|
@@ -2829,18 +3848,18 @@ async function main() {
|
|
|
2829
3848
|
console.error("Design companion accepts standalone .html files.");
|
|
2830
3849
|
process.exit(1);
|
|
2831
3850
|
}
|
|
2832
|
-
if (!
|
|
3851
|
+
if (!existsSync6(abs)) {
|
|
2833
3852
|
console.error(`File not found: ${abs}`);
|
|
2834
3853
|
process.exit(1);
|
|
2835
3854
|
}
|
|
2836
|
-
const dest =
|
|
2837
|
-
|
|
3855
|
+
const dest = join12(contentDir, basename2(abs));
|
|
3856
|
+
copyFileSync3(abs, dest);
|
|
2838
3857
|
console.log(`Design pushed: ${dest}`);
|
|
2839
3858
|
}
|
|
2840
3859
|
console.log(`Preview: http://localhost:${readDashboardPort()}/design`);
|
|
2841
3860
|
} else if (designSub === "clear") {
|
|
2842
|
-
const files =
|
|
2843
|
-
for (const f of files) rmSync4(
|
|
3861
|
+
const files = readdirSync5(contentDir);
|
|
3862
|
+
for (const f of files) rmSync4(join12(contentDir, f), { force: true });
|
|
2844
3863
|
console.log("Design companion content cleared.");
|
|
2845
3864
|
} else {
|
|
2846
3865
|
console.log("ijfw design -- Manage live preview and durable design intelligence.");
|
|
@@ -2853,26 +3872,26 @@ async function main() {
|
|
|
2853
3872
|
case "help": {
|
|
2854
3873
|
const wantsBrowser = argv.slice(3).includes("--browser");
|
|
2855
3874
|
const candidates = [
|
|
2856
|
-
|
|
3875
|
+
join12(repoRoot(), "docs", "GUIDE.md"),
|
|
2857
3876
|
resolve4(__dirname2, "..", "docs", "GUIDE.md"),
|
|
2858
|
-
|
|
3877
|
+
join12(homedir(), ".ijfw", "docs", "GUIDE.md")
|
|
2859
3878
|
];
|
|
2860
|
-
const guidePath = candidates.find((p) =>
|
|
3879
|
+
const guidePath = candidates.find((p) => existsSync6(p));
|
|
2861
3880
|
if (!guidePath) {
|
|
2862
3881
|
console.error("[ijfw] Guide not found. Run `ijfw install` to fetch the full guide, or visit https://gitlab.com/therealseandonahoe/ijfw/-/blob/main/docs/GUIDE.md");
|
|
2863
3882
|
process.exit(1);
|
|
2864
3883
|
}
|
|
2865
3884
|
if (wantsBrowser) {
|
|
2866
3885
|
const { marked } = await Promise.resolve().then(() => (init_marked_esm(), marked_esm_exports));
|
|
2867
|
-
const assetsSrc =
|
|
2868
|
-
const outDir =
|
|
2869
|
-
|
|
2870
|
-
if (
|
|
2871
|
-
for (const f of
|
|
2872
|
-
|
|
3886
|
+
const assetsSrc = join12(dirname4(guidePath), "guide", "assets");
|
|
3887
|
+
const outDir = join12(homedir(), ".ijfw", "guide");
|
|
3888
|
+
mkdirSync5(join12(outDir, "assets"), { recursive: true });
|
|
3889
|
+
if (existsSync6(assetsSrc)) {
|
|
3890
|
+
for (const f of readdirSync5(assetsSrc)) {
|
|
3891
|
+
copyFileSync3(join12(assetsSrc, f), join12(outDir, "assets", f));
|
|
2873
3892
|
}
|
|
2874
3893
|
}
|
|
2875
|
-
const md =
|
|
3894
|
+
const md = readFileSync6(guidePath, "utf8").replace(/\(guide\/assets\//g, "(assets/");
|
|
2876
3895
|
const rendered = marked.parse(md, { gfm: true, breaks: false });
|
|
2877
3896
|
const html = `<!doctype html>
|
|
2878
3897
|
<html lang="en"><head>
|
|
@@ -2889,8 +3908,8 @@ async function main() {
|
|
|
2889
3908
|
table{display:table;width:100%}
|
|
2890
3909
|
</style>
|
|
2891
3910
|
</head><body><div class="wrap markdown-body">${rendered}</div></body></html>`;
|
|
2892
|
-
const outHtml =
|
|
2893
|
-
|
|
3911
|
+
const outHtml = join12(outDir, "index.html");
|
|
3912
|
+
writeFileSync6(outHtml, html);
|
|
2894
3913
|
const opener = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
|
|
2895
3914
|
spawnSync12(opener, [outHtml], { stdio: "ignore", detached: true });
|
|
2896
3915
|
console.log(`[ijfw] Guide opened in your browser.`);
|
|
@@ -2901,10 +3920,10 @@ async function main() {
|
|
|
2901
3920
|
if (hasLess) {
|
|
2902
3921
|
const lessRes = spawnSync12("less", ["-R", guidePath], { stdio: "inherit" });
|
|
2903
3922
|
if (lessRes.status !== 0 && lessRes.status !== null) {
|
|
2904
|
-
process.stdout.write(
|
|
3923
|
+
process.stdout.write(readFileSync6(guidePath, "utf8"));
|
|
2905
3924
|
}
|
|
2906
3925
|
} else {
|
|
2907
|
-
process.stdout.write(
|
|
3926
|
+
process.stdout.write(readFileSync6(guidePath, "utf8"));
|
|
2908
3927
|
}
|
|
2909
3928
|
process.exit(0);
|
|
2910
3929
|
break;
|