File size: 4,648 Bytes
a6b96c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
"use strict";
/**
 * Capability activation helpers.
 *
 * Shared by the Capability State Resolver and Loop Resolver so config-key
 * activation uses one precedence chain and one prototype-pollution guard.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
// eslint-disable-next-line @typescript-eslint/no-require-imports
const planningWorkspaceMod = require("./planning-workspace.cjs");
const { planningDir, planningRoot } = planningWorkspaceMod;
function _getNestedConfigValue(config, dotKey) {
    const segments = dotKey.split('.');
    let current = config;
    for (const seg of segments) {
        if (seg === '__proto__' || seg === 'constructor' || seg === 'prototype') {
            return { found: false, value: undefined };
        }
        if (typeof current !== 'object' || current === null) {
            return { found: false, value: undefined };
        }
        const cur = current;
        if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
            return { found: false, value: undefined };
        }
        current = cur[seg];
    }
    return { found: true, value: current };
}
const _warnedRawConfigPaths = new Set();
function _readRawConfigKey(filePath, dotKey) {
    try {
        const raw = node_fs_1.default.readFileSync(filePath, 'utf8');
        let parsed;
        try {
            parsed = JSON.parse(raw);
        }
        catch {
            if (!_warnedRawConfigPaths.has(filePath)) {
                _warnedRawConfigPaths.add(filePath);
                try {
                    process.stderr.write(`gsd-tools: warning: failed to parse ${filePath} as JSON — skipping for activation resolution\n`);
                }
                catch { /* stderr might be closed */ }
            }
            return { found: false, value: undefined };
        }
        return _getNestedConfigValue(parsed, dotKey);
    }
    catch {
        return { found: false, value: undefined };
    }
}
/**
 * Resolve the raw value for a dotted config key using the four-level precedence
 * walk. Returns { found, value } with the RAW value (not coerced to boolean),
 * so callers can decide how to interpret the value (boolean gate vs. raw config
 * value for numeric/string settings like security_asvs_level).
 *
 * Precedence (mirrors _resolveActivationValue):
 *   1. loadConfig result (config arg) — guarded nested-lookup.
 *   2. Workstream config.json at planningDir(cwd)/config.json.
 *   3. Root config.json at planningRoot(cwd)/config.json (only if path differs).
 *   4. registry.configSchema[dotKey].default — schema default.
 *   5. Absent → { found: false, value: undefined }.
 */
function resolveConfigKey(dotKey, opts) {
    const { config, cwd, registry } = opts;
    // Level 1: loadConfig result
    const fromConfig = _getNestedConfigValue(config, dotKey);
    if (fromConfig.found)
        return { found: true, value: fromConfig.value };
    // Level 2 + 3: raw config.json files (only when cwd is available)
    if (cwd) {
        const wsConfigPath = node_path_1.default.join(planningDir(cwd), 'config.json');
        const rootConfigPath = node_path_1.default.join(planningRoot(cwd), 'config.json');
        const fromWs = _readRawConfigKey(wsConfigPath, dotKey);
        if (fromWs.found)
            return { found: true, value: fromWs.value };
        if (wsConfigPath !== rootConfigPath) {
            const fromRoot = _readRawConfigKey(rootConfigPath, dotKey);
            if (fromRoot.found)
                return { found: true, value: fromRoot.value };
        }
    }
    // Level 4: registry configSchema default
    const schemaMap = registry['configSchema'];
    if (schemaMap && typeof schemaMap === 'object' && !Array.isArray(schemaMap)
        && Object.prototype.hasOwnProperty.call(schemaMap, dotKey)) {
        const schemaEntry = schemaMap[dotKey];
        if (schemaEntry && typeof schemaEntry === 'object' && schemaEntry !== null) {
            const def = schemaEntry['default'];
            if (def !== undefined)
                return { found: true, value: def };
        }
    }
    // Level 5: absent
    return { found: false, value: undefined };
}
function _resolveActivationValue(dotKey, config, cwd, registry) {
    const r = resolveConfigKey(dotKey, { config, cwd, registry });
    return r.found ? Boolean(r.value) : false;
}
module.exports = {
    _getNestedConfigValue,
    _readRawConfigKey,
    _resolveActivationValue,
    resolveConfigKey,
};