@untangle-code/config 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/NOTICE +90 -0
- package/README.md +262 -2
- package/THIRD_PARTY_LICENSES.md +29 -0
- package/dist/chunker-budget.d.ts +58 -0
- package/dist/chunker-budget.d.ts.map +1 -0
- package/dist/chunker-budget.js +11 -0
- package/dist/chunker-budget.js.map +1 -0
- package/dist/defaults.d.ts +50 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +179 -0
- package/dist/defaults.js.map +1 -0
- package/dist/discovery.d.ts +40 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +76 -0
- package/dist/discovery.js.map +1 -0
- package/dist/env.d.ts +37 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +100 -0
- package/dist/env.js.map +1 -0
- package/dist/hash.d.ts +37 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +52 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/load.d.ts +61 -0
- package/dist/load.d.ts.map +1 -0
- package/dist/load.js +264 -0
- package/dist/load.js.map +1 -0
- package/dist/merge.d.ts +32 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +65 -0
- package/dist/merge.js.map +1 -0
- package/dist/migrations.d.ts +26 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +40 -0
- package/dist/migrations.js.map +1 -0
- package/dist/parse.d.ts +7 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +14 -0
- package/dist/parse.js.map +1 -0
- package/dist/paths.d.ts +14 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +37 -0
- package/dist/paths.js.map +1 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +19 -0
- package/dist/schema.js.map +1 -0
- package/dist/secrets.d.ts +33 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +69 -0
- package/dist/secrets.js.map +1 -0
- package/dist/types.d.ts +323 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/user-config.d.ts +13 -0
- package/dist/user-config.d.ts.map +1 -0
- package/dist/user-config.js +34 -0
- package/dist/user-config.js.map +1 -0
- package/dist/validate.d.ts +36 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +337 -0
- package/dist/validate.js.map +1 -0
- package/package.json +65 -3
- package/schemas/config-v1.json +375 -0
- package/src/chunker-budget.ts +63 -0
- package/src/defaults.ts +207 -0
- package/src/discovery.ts +95 -0
- package/src/env.ts +141 -0
- package/src/hash.ts +71 -0
- package/src/index.ts +51 -0
- package/src/load.ts +346 -0
- package/src/merge.ts +69 -0
- package/src/migrations.ts +64 -0
- package/src/parse.ts +18 -0
- package/src/paths.ts +42 -0
- package/src/schema.ts +23 -0
- package/src/secrets.ts +78 -0
- package/src/types.ts +366 -0
- package/src/user-config.ts +48 -0
- package/src/validate.ts +430 -0
package/NOTICE
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# NOTICE
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2026 Luke Bölling and contributors.
|
|
4
|
+
|
|
5
|
+
This attribution covers original Untangle work. Contributors retain copyright in
|
|
6
|
+
their contributions. Third-party packages, grammars and other materials retain
|
|
7
|
+
their own copyright holders, licenses and notices; this line does not replace them.
|
|
8
|
+
|
|
9
|
+
## License — binding decision
|
|
10
|
+
|
|
11
|
+
Untangle is licensed under the **GNU Affero General Public License, Version 3
|
|
12
|
+
(AGPL-3.0-only)**. See [`LICENSE`](./LICENSE).
|
|
13
|
+
|
|
14
|
+
This decision is **final, not provisional**, and was explicitly instructed by
|
|
15
|
+
the project owner. No contributor, agent or tool may change it without a new
|
|
16
|
+
explicit instruction.
|
|
17
|
+
|
|
18
|
+
The repository-wide license covers Untangle's first-party source code, scripts,
|
|
19
|
+
tests, fixtures, configuration, documentation, website and brand artwork, including
|
|
20
|
+
the logo and 3D yarn illustrations. Every workspace `package.json` declares
|
|
21
|
+
`"license": "AGPL-3.0-only"`. There is no separately licensed enterprise component
|
|
22
|
+
or separate permissive license for documentation or brand assets.
|
|
23
|
+
|
|
24
|
+
Third-party code, embedded grammars, dependency assets and their notices retain
|
|
25
|
+
their original licenses. Their inclusion does not relicense those works. The
|
|
26
|
+
repository license does not grant trademark rights or change the license of a
|
|
27
|
+
repository analyzed with Untangle. See the repository's `docs/LICENSING.md` for
|
|
28
|
+
scope and verification details.
|
|
29
|
+
|
|
30
|
+
This resolves the license question left open in `vision.md` §14 and completes
|
|
31
|
+
`product.md` §16, item 2.
|
|
32
|
+
|
|
33
|
+
## Rationale
|
|
34
|
+
|
|
35
|
+
- **Network copyleft fits the intended product model.** AGPL §13 sets source
|
|
36
|
+
availability requirements for modified versions used through a computer network.
|
|
37
|
+
The current local CLI/stdio-MCP entry and future shared-server modes share the
|
|
38
|
+
same license.
|
|
39
|
+
- **Local-first analysis has a clear boundary.** Running Untangle against a codebase
|
|
40
|
+
does not replace that codebase's own license with Untangle's license.
|
|
41
|
+
- **First-party components share one license.** Implemented components and future
|
|
42
|
+
official plugins use this repository's licensing policy. Planned integrations do
|
|
43
|
+
not establish implemented or published products.
|
|
44
|
+
|
|
45
|
+
## Deliberately accepted consequence
|
|
46
|
+
|
|
47
|
+
The project deliberately chooses AGPL rather than a permissive embedding license.
|
|
48
|
+
Redistribution, modifications and combined works must be assessed against the
|
|
49
|
+
actual terms in `LICENSE`, including applicable Corresponding Source requirements.
|
|
50
|
+
No permissive exception, commercial dual license or special contract-only package
|
|
51
|
+
license is granted or promised by this notice.
|
|
52
|
+
|
|
53
|
+
## Dependencies
|
|
54
|
+
|
|
55
|
+
Third-party runtime dependencies are checked against the allowlist in
|
|
56
|
+
`scripts/check-licenses.mjs`. Their licenses and copyright notices remain unchanged
|
|
57
|
+
and traceable through package `THIRD_PARTY_LICENSES.md` files, grammar provenance,
|
|
58
|
+
the lockfile and release SBOM. The allowlist includes MIT / Apache-2.0 / BSD / ISC
|
|
59
|
+
and other explicitly listed licenses; it is not a license grant for dependencies.
|
|
60
|
+
|
|
61
|
+
The allowlist in `scripts/check-licenses.mjs` applies to **dependencies**, not
|
|
62
|
+
workspace packages. Untangle's own packages carry AGPL-3.0-only and are excluded
|
|
63
|
+
from that check.
|
|
64
|
+
|
|
65
|
+
## Clean room
|
|
66
|
+
|
|
67
|
+
Untangle is an independent implementation with its own data models, APIs, tool
|
|
68
|
+
names, tests and sources. Feature ideas may be derived from publicly observable
|
|
69
|
+
behavior and generally known techniques. Third-party source code is not copied,
|
|
70
|
+
including from AGPL projects even where the license would permit it.
|
|
71
|
+
|
|
72
|
+
## Contribution terms
|
|
73
|
+
|
|
74
|
+
Pull requests are welcome under the repository's existing AGPL-3.0-only license.
|
|
75
|
+
No separate CLA, copyright assignment or DCO sign-off is required. Contributors
|
|
76
|
+
must have the right to submit their work under those terms. See CONTRIBUTING.md.
|
|
77
|
+
|
|
78
|
+
## Release decisions
|
|
79
|
+
|
|
80
|
+
- [x] Copyright attribution: Luke Bölling, with contributors retaining their rights.
|
|
81
|
+
- [x] Contributions use AGPL-3.0-only without an additional CLA or DCO gate.
|
|
82
|
+
|
|
83
|
+
## Open legal items before public beta
|
|
84
|
+
|
|
85
|
+
- [ ] Trademark search for the product name `Untangle`.
|
|
86
|
+
- [ ] Registration and legal review of the npm scope `@untangle-code`.
|
|
87
|
+
- [ ] Verify source availability and notices for each released network daemon/server
|
|
88
|
+
mode (WP-59). The current stdio MCP `untangle_about` already reports the license,
|
|
89
|
+
source repository and build commit when supplied; metadata alone does not prove
|
|
90
|
+
that a concrete deployed version's source is available.
|
package/README.md
CHANGED
|
@@ -1,3 +1,263 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @untangle-code/config
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Untangle configuration (WP-02): types, discovery, six-layer resolution, handwritten JSON Schema,
|
|
4
|
+
secret redaction, canonical effective configuration with hashes and `embedding_profile_id`, and
|
|
5
|
+
migration infrastructure for future `schemaVersion` changes. A public package
|
|
6
|
+
(`@untangle-code/config`, product.md §6.2).
|
|
7
|
+
|
|
8
|
+
Normative sources: [`spec.md`](../../spec.md) §4.1–§4.8, §5.7 (takes precedence),
|
|
9
|
+
[`product.md`](../../product.md) §11, [`architecture.md`](../../architecture.md) §6.3, §23.2,
|
|
10
|
+
[`portfolio.md`](../../portfolio.md) §13, §14.4.
|
|
11
|
+
|
|
12
|
+
`src/index.ts` is the only public interface. Its sole runtime dependency is
|
|
13
|
+
`@untangle-code/domain` (`workspace:*`); no other Untangle or external runtime dependencies.
|
|
14
|
+
|
|
15
|
+
<a id="modulübersicht"></a>
|
|
16
|
+
|
|
17
|
+
## Modules
|
|
18
|
+
|
|
19
|
+
| Module | Contents | Normative source |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `types.ts` | `UntangleConfigV1`, `QdrantConfig`, `EmbeddingConfig`, `IndexConfig`, `SearchConfig`, `GraphConfig`, `ComplexityConfig`, `ChangeConfig`, `QualityGateConfig`, `TelemetryConfig`, `PortfolioConfig`/`PortfolioEnrichmentConfig`, `EffectiveUntangleConfigV1`, `RawConfigLayer`/`DeepPartial`; re-exports domain's `RepositoryConfig`/`ContextArtifactConfig` | spec §4.2–§4.7; portfolio §13, §14.4 |
|
|
22
|
+
| `defaults.ts` | Defaults per section, `PRODUCT_DEFAULT_CONFIG` (layer 6), `CURRENT_SCHEMA_VERSION` | spec §4.5; product §11 |
|
|
23
|
+
| `schema.ts` | Parsed `CONFIG_SCHEMA` and `CONFIG_SCHEMA_PATH` | arch §6.3, §7.3 |
|
|
24
|
+
| `discovery.ts` | `discoverConfigFile`, `discoverConfigFiles`: upward discovery to repository/filesystem root | spec §4.1 |
|
|
25
|
+
| `user-config.ts` | `resolveUserConfigDir`/`resolveUserConfigPath`: OS configuration directory | spec §4.1 layer 5 |
|
|
26
|
+
| `env.ts` | `ENV_MAPPING`, `buildEnvConfigLayer`: `UNTANGLE_*` to config paths | spec §4.1 layer 2 |
|
|
27
|
+
| `merge.ts` | `deepMergeConfig`, `mergeConfigLayers`: replace arrays, never concatenate | spec §4.1 |
|
|
28
|
+
| `paths.ts` | Canonical `resolveConfigPath`, `resolveRepositoryPaths`, relative to configuration file | spec §4.3 |
|
|
29
|
+
| `secrets.ts` | `redactSecrets`, `toRedactedJson`, `resolveApiKey` | spec §4.4/§4.5, §24; arch §23.2 |
|
|
30
|
+
| `hash.ts` | `computeConfigHash`, `deriveEmbeddingProfile`/`deriveEmbeddingProfileId` | arch §6.3; spec §5.1, §5.7 |
|
|
31
|
+
| `validate.ts` | `validateEffectiveConfig`, `assertConfigValid`, `assertEmbeddingDimensionsMatch` | spec §4.8 |
|
|
32
|
+
| `migrations.ts` | `registerMigration`, `migrateConfig`, `clearMigrations` | arch §6.3 |
|
|
33
|
+
| `load.ts` | `loadConfig`: orchestrates these modules | spec §4.1–§4.8 |
|
|
34
|
+
|
|
35
|
+
## Layering (spec.md §4.1)
|
|
36
|
+
|
|
37
|
+
Highest priority first; `loadConfig` merges in the following order, lowest first and last wins:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
1. Explicit API/CLI arguments (loadConfig({ explicit }))
|
|
41
|
+
2. Environment variables (UNTANGLE_*, see env.ts ENV_MAPPING)
|
|
42
|
+
3. .untangle.local.json (upward discovery from cwd)
|
|
43
|
+
4. .untangle.json (upward discovery from cwd)
|
|
44
|
+
5. User configuration (OS directory) (user-config.ts)
|
|
45
|
+
6. Product defaults (defaults.ts PRODUCT_DEFAULT_CONFIG)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Merge semantics: objects merge recursively; **arrays replace completely**, never concatenate.
|
|
49
|
+
Replacing a full list such as `exclude` is the only way a higher layer can shrink it; silently
|
|
50
|
+
combining array sources would be unpredictable.
|
|
51
|
+
|
|
52
|
+
`.untangle.local.json` and `.untangle.json` are discovered **independently**, each searching upward
|
|
53
|
+
from `cwd` to the repository root (a directory containing `.git`) or filesystem root, whichever
|
|
54
|
+
comes first.
|
|
55
|
+
|
|
56
|
+
<a id="untangle_-umgebungsvariablen-auszug-additiv-erweiterbar"></a>
|
|
57
|
+
|
|
58
|
+
## `UNTANGLE_*` environment variables (excerpt, additively extensible)
|
|
59
|
+
|
|
60
|
+
| Variable | Configuration path |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `UNTANGLE_WORKSPACE_ID` | `workspaceId` |
|
|
63
|
+
| `UNTANGLE_PROJECT_ID` | `projectId` |
|
|
64
|
+
| `UNTANGLE_STORAGE_BACKEND` | `storage.backend` (`lancedb` or `qdrant`) |
|
|
65
|
+
| `UNTANGLE_LANCEDB_PATH` | `storage.path` (local directory) |
|
|
66
|
+
| `UNTANGLE_QDRANT_MODE` / `_URL` / `_API_KEY_ENV` / `_COLLECTION_PREFIX` / `_REQUEST_TIMEOUT_MS` / `_MAX_RETRIES` | `qdrant.*` |
|
|
67
|
+
| `UNTANGLE_EMBEDDINGS_MODE` / `_URL` / `_API_KEY_ENV` / `_MODEL` / `_DIMENSIONS` / `_REQUEST_TIMEOUT_MS` | `embeddings.*` |
|
|
68
|
+
| `UNTANGLE_INDEX_WATCH` / `_MAX_FILE_SIZE_MB` / `_EXTRA_EXTENSIONS` (CSV) | `index.*` |
|
|
69
|
+
| `UNTANGLE_QUALITY_GATE_MODE` / `_MINIMUM_PARSE_QUALITY` | `qualityGate.*` |
|
|
70
|
+
| `UNTANGLE_TELEMETRY_ENABLED` / `_LOG_LEVEL` | `telemetry.*` |
|
|
71
|
+
| `UNTANGLE_PORTFOLIO_ENABLED` | `portfolio.enabled` |
|
|
72
|
+
|
|
73
|
+
The complete machine-readable table is `ENV_MAPPING` in `src/env.ts`. `*_API_KEY_ENV` sets another
|
|
74
|
+
environment variable's **name**, never its secret value (see Secrets).
|
|
75
|
+
|
|
76
|
+
## Search storage
|
|
77
|
+
|
|
78
|
+
New projects use embedded LanceDB at `.untangle/index/lancedb`. A relative `storage.path`
|
|
79
|
+
resolves against the repository root, including when configuration lives in
|
|
80
|
+
`.untangle/config.json`. Absolute local directories are accepted; URI schemes and network
|
|
81
|
+
paths are rejected.
|
|
82
|
+
|
|
83
|
+
Existing projects that explicitly configure a `qdrant` section or a mapped
|
|
84
|
+
`UNTANGLE_QDRANT_*` variable retain Qdrant. An explicit `storage.backend` overrides that
|
|
85
|
+
compatibility default, with the ordinary layer precedence. Setting only `storage.path`
|
|
86
|
+
does not move an existing Qdrant project. Qdrant connection options remain under `qdrant`:
|
|
87
|
+
|
|
88
|
+
```toml
|
|
89
|
+
[storage]
|
|
90
|
+
backend = "qdrant"
|
|
91
|
+
|
|
92
|
+
[qdrant]
|
|
93
|
+
mode = "external"
|
|
94
|
+
url = "http://127.0.0.1:16333"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`parseConfigText(text, "json" | "toml")` parses and migrates an existing document without
|
|
98
|
+
filesystem access, product defaults or environment overrides. Callers such as `init` can
|
|
99
|
+
preserve declared storage choices while inspecting the original configuration.
|
|
100
|
+
|
|
101
|
+
## Secrets (spec.md §4.4/§4.5, §24; architecture.md §23.2)
|
|
102
|
+
|
|
103
|
+
`QdrantConfig.apiKeyEnv` / `EmbeddingConfig.apiKeyEnv` store only environment variable **names**, so
|
|
104
|
+
effective configuration structurally never contains secret values. `resolveApiKey(envVarName, env)`
|
|
105
|
+
resolves values at runtime on demand; results must never be written back to configuration objects.
|
|
106
|
+
`redactSecrets`/`toRedactedJson` provide additional protection: every field resembling a resolved
|
|
107
|
+
secret (`apiKey`, `token`, `secret`, `password`, `credential`, but NOT `...Env`) is replaced with
|
|
108
|
+
`[REDACTED]` before serialization, even if accidentally attached to configuration. Verified in
|
|
109
|
+
`secrets.test.ts`.
|
|
110
|
+
|
|
111
|
+
<a id="validierung-specmd-48"></a>
|
|
112
|
+
|
|
113
|
+
## Validation (spec.md §4.8)
|
|
114
|
+
|
|
115
|
+
`loadConfig` **never** throws for warnings or errors; it returns
|
|
116
|
+
`{ effectiveConfig, warnings, errors }`. Unknown top-level fields warn with the field name as path.
|
|
117
|
+
Invalid enums, unresolved core values (such as missing `qdrant.url` with `mode: "external"`),
|
|
118
|
+
duplicate repository/artifact IDs and `portfolio.blueprint.weights` sums outside `1.0 ± 0.001`
|
|
119
|
+
produce `CONFIG_INVALID`. Aborting is deliberately the caller's choice:
|
|
120
|
+
`assertConfigValid(result.errors)` throws a domain `UntangleError`.
|
|
121
|
+
`assertEmbeddingDimensionsMatch(configured, existing)` checks conflicts before the first upsert
|
|
122
|
+
(`EMBEDDING_DIMENSION_MISMATCH`).
|
|
123
|
+
|
|
124
|
+
<a id="korpusgebundene-relevanz-policies-wp-231"></a>
|
|
125
|
+
|
|
126
|
+
### Corpus-bound relevance policies (WP-231)
|
|
127
|
+
|
|
128
|
+
`search.relevancePolicies` deliberately has **no product default**. Raw cosine values and their
|
|
129
|
+
separation depend jointly on embedding profile and indexed corpus. Each entry is therefore keyed
|
|
130
|
+
by complete `embeddingProfileId` and bound to exactly one `workspaceId` and physical `collection`:
|
|
131
|
+
|
|
132
|
+
```toml
|
|
133
|
+
workspaceId = "workspace-stable-id"
|
|
134
|
+
|
|
135
|
+
[search.relevancePolicies."complete-embedding-profile-id"]
|
|
136
|
+
workspaceId = "workspace-stable-id"
|
|
137
|
+
collection = "untangle_project_code_profile_v1"
|
|
138
|
+
|
|
139
|
+
[search.relevancePolicies."complete-embedding-profile-id".policy]
|
|
140
|
+
calibrationId = "model/corpus/revision"
|
|
141
|
+
minimumDenseScore = 0.45
|
|
142
|
+
minimumDenseSpread = 0.061
|
|
143
|
+
denseReferenceRank = 50
|
|
144
|
+
minimumCandidateIdfCoverage = 0.291
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
If any profile/workspace/collection scope fails to match, search discards no results and reports
|
|
148
|
+
`RELEVANCE_POLICY_NOT_EVALUATED`. The same applies when retrieval did not actually reach the
|
|
149
|
+
calibrated dense reference rank or the sparse branch is missing. IDF evidence uses fixed
|
|
150
|
+
dense/sparse prefixes through the reference rank, so raising `candidateLimit` cannot move the
|
|
151
|
+
calibrated yes/no decision. Significant corpus changes require remeasurement. An already-indexed
|
|
152
|
+
workspace should declare a top-level `workspaceId`, otherwise adding the policy changes the
|
|
153
|
+
configuration hash and thus the derived workspace ID itself.
|
|
154
|
+
|
|
155
|
+
<a id="kanonische-effective-config-hash-und-embedding_profile_id"></a>
|
|
156
|
+
|
|
157
|
+
## Canonical effective configuration, hash and `embedding_profile_id`
|
|
158
|
+
|
|
159
|
+
`computeConfigHash(effectiveConfig)` equals `SHA256(canonical_json(effectiveConfig))`, using
|
|
160
|
+
domain's `canonicalJson`/`sha256Hex`, for LanceDB. For Qdrant, the additive `storage` section is
|
|
161
|
+
excluded so existing configuration hashes and derived workspace identities remain unchanged;
|
|
162
|
+
the endpoint and other connection options remain represented by `qdrant`. Without explicit `workspaceId`, `loadConfig` uses that hash as
|
|
163
|
+
`configHash` input to `buildWorkspaceId` (spec §5.1). `deriveEmbeddingProfile`/
|
|
164
|
+
`deriveEmbeddingProfileId` construct domain's `EmbeddingProfile` from `EmbeddingConfig` plus fixed
|
|
165
|
+
`DEFAULT_EMBEDDING_NORMALIZATION` (`"none"`) and `PREPARED_TEXT_VERSION`
|
|
166
|
+
(`"untangle-prepared-text-v1"`), then call `buildEmbeddingProfileId` (spec §5.7).
|
|
167
|
+
|
|
168
|
+
## Migration (architecture.md §6.3)
|
|
169
|
+
|
|
170
|
+
`registerMigration({ fromVersion, toVersion, migrate })` registers one step;
|
|
171
|
+
`migrateConfig(raw, targetVersion)` chains registered steps from `raw.schemaVersion` (default `1`)
|
|
172
|
+
to `targetVersion` (default `CURRENT_SCHEMA_VERSION`). Configuration already at the target version
|
|
173
|
+
undergoes **identity migration** and returns unchanged without requiring registered steps (tested).
|
|
174
|
+
`loadConfig` migrates each raw file configuration separately before merging it.
|
|
175
|
+
|
|
176
|
+
<a id="entscheidungen"></a>
|
|
177
|
+
|
|
178
|
+
## Decisions
|
|
179
|
+
|
|
180
|
+
1. **`RepositoryConfig`/`ContextArtifactConfig` re-export domain types instead of duplicating
|
|
181
|
+
them.** Domain README decision 5 explicitly requires this for `RepositoryConfig`; the same
|
|
182
|
+
applies to `ContextArtifactConfig` (BUILD_PLAN §3.3 lists it among domain core contracts).
|
|
183
|
+
|
|
184
|
+
2. **`SearchConfig`, `GraphConfig`, `ComplexityConfig`, `ChangeConfig` are designed here.**
|
|
185
|
+
Spec.md §4.2 names them without shapes, unlike §4.4–§4.7's four concrete infrastructure/index/
|
|
186
|
+
quality schemas. `TelemetryConfig` is likewise unspecified beyond mandatory default-off
|
|
187
|
+
behavior (spec §24). Each shape references its closest normative source in `types.ts`:
|
|
188
|
+
`SearchConfig` uses `SearchRequestV1`/§13.3 boosts, `GraphConfig` architecture.md §17.4 traversal
|
|
189
|
+
limits, `ComplexityConfig` product.md §11's example, and `ChangeConfig` spec §17.6 risk defaults.
|
|
190
|
+
Downstream work may extend them additively, not redefine them locally.
|
|
191
|
+
|
|
192
|
+
3. **`PortfolioConfig` follows portfolio.md §14.4 verbatim**, including every default; its required
|
|
193
|
+
±0.001 sum validation for `blueprint.weights` is implemented in `validate.ts`.
|
|
194
|
+
|
|
195
|
+
4. **JSON Schema lives in `packages/config/schemas/config-v1.json`, not root `schemas/`.**
|
|
196
|
+
BUILD_PLAN §4 also assigns `schemas/**` to WP-02, but this package's task explicitly limited
|
|
197
|
+
writes to `packages/config/**`. As noted in `openIssues`, an authorized owner must mirror it
|
|
198
|
+
to `schemas/config-v1.json`. `schema.ts` resolves `../schemas/config-v1.json` from
|
|
199
|
+
`import.meta.url`, working from both `src/` tests and `dist/` builds because both are siblings
|
|
200
|
+
of `schemas/`.
|
|
201
|
+
|
|
202
|
+
5. **`additionalProperties: false` in JSON Schema despite spec §4.8's warn-not-fail rule.** The
|
|
203
|
+
handwritten schema is tooling for editor completion/external validators; this package does not
|
|
204
|
+
run it through a runtime schema validator such as ajv. `validate.ts` implements exact warning/
|
|
205
|
+
abort semantics from spec §4.8 directly in TypeScript without another runtime dependency. The schema documents
|
|
206
|
+
this decision in `description`.
|
|
207
|
+
|
|
208
|
+
6. **`normalization` and `preparedTextVersion` are not `EmbeddingConfig` fields but are required
|
|
209
|
+
for `embedding_profile_id`.** Spec §4.5 lists exactly ten fields excluding them, while §5.7
|
|
210
|
+
requires them for profile identity. Fixed defaults in `defaults.ts` supply
|
|
211
|
+
`DEFAULT_EMBEDDING_NORMALIZATION = "none"` and
|
|
212
|
+
`PREPARED_TEXT_VERSION = "untangle-prepared-text-v1"`; `deriveEmbeddingProfile`'s second
|
|
213
|
+
argument can override them for future configuration extensions.
|
|
214
|
+
|
|
215
|
+
7. **Deep-partial raw layers (`RawConfigLayer`/`DeepPartial<T>`).** An individual file/environment/
|
|
216
|
+
argument layer rarely supplies complete `QdrantConfig`/`EmbeddingConfig`; a shallow
|
|
217
|
+
`Partial<UntangleConfigV1>` would still require every field within any supplied child object.
|
|
218
|
+
`DeepPartial` makes every field recursively optional; after merging product defaults,
|
|
219
|
+
`validate.ts` enforces actual requirements. Its condition deliberately uses `T extends object`
|
|
220
|
+
rather than `T extends Record<string, unknown>`: the latter is structurally false for
|
|
221
|
+
interfaces lacking index signatures in TypeScript conditional types, a verified compiler trap.
|
|
222
|
+
|
|
223
|
+
8. **`loadConfig` never throws itself.** File read errors, invalid JSON and validation failures all
|
|
224
|
+
enter `errors`/`warnings`; only explicitly invoked `assertConfigValid` throws
|
|
225
|
+
`UntangleError("CONFIG_INVALID", …)`. This follows the requested result-object rather than
|
|
226
|
+
exception behavior for warnings, consistently extended to errors so a CLI can collect and
|
|
227
|
+
present every issue before aborting.
|
|
228
|
+
|
|
229
|
+
9. **Discovery stops at a `.git` directory entry OR filesystem root.** Spec §4.1 names repository/
|
|
230
|
+
filesystem roots without defining repository detection. A directory containing `.git` as a file
|
|
231
|
+
or directory, supporting Git worktrees, is a repository root: it is searched, its parent is not.
|
|
232
|
+
No `GitPort` is needed; filesystem existence checks suffice, and discovery already needs filesystem access.
|
|
233
|
+
|
|
234
|
+
10. **JSON Schema uses `schemaVersion` `const: 1`, while `RawConfigLayer` uses `number`.** Raw
|
|
235
|
+
unmigrated files may carry any historical version, read by `migrateConfig` before merge.
|
|
236
|
+
JSON Schema describes the *current* v1 tooling contract.
|
|
237
|
+
|
|
238
|
+
<a id="verifikation"></a>
|
|
239
|
+
|
|
240
|
+
## Verification
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
pnpm --filter @untangle-code/config run typecheck # tsc, 0 errors
|
|
244
|
+
pnpm --filter @untangle-code/config run test # vitest, 73 tests passed, 1 skipped (Windows path test on POSIX)
|
|
245
|
+
pnpm --filter @untangle-code/config run build # emit to dist/
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
<a id="lizenz"></a>
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
**AGPL-3.0-only.** This package includes the complete license in [`LICENSE`](./LICENSE), the
|
|
254
|
+
license-choice rationale in [`NOTICE`](./NOTICE), and licenses for all third-party production
|
|
255
|
+
dependencies in [`THIRD_PARTY_LICENSES.md`](./THIRD_PARTY_LICENSES.md). All three ship in the npm
|
|
256
|
+
tarball: AGPL-3.0-only §4 and §5 require recipients to receive the license; a README alone is not
|
|
257
|
+
a copy of the license.
|
|
258
|
+
|
|
259
|
+
Local use alone does not trigger an obligation. Copyleft obligations apply to redistribution and
|
|
260
|
+
providing a network service (AGPL §13).
|
|
261
|
+
|
|
262
|
+
Corresponding TypeScript source ships under `src/` in the same tarball; the complete repository
|
|
263
|
+
is available at <https://github.com/lboel/untangle>.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Third-party licenses — @untangle-code/config
|
|
2
|
+
|
|
3
|
+
Generated by `pnpm third-party-licenses` from this package's installed
|
|
4
|
+
production dependency tree: transitive `dependencies`, across
|
|
5
|
+
workspace edges, with no `devDependencies` included. The generator reads
|
|
6
|
+
each resolved package's `package.json` in `node_modules`; nothing is maintained by hand
|
|
7
|
+
or estimated.
|
|
8
|
+
|
|
9
|
+
Do not edit manually: `pnpm third-party-licenses --check` fails on drift,
|
|
10
|
+
and `pnpm check-publishable` rejects packages without a current file.
|
|
11
|
+
|
|
12
|
+
This file exists because `docs/BUILD_PLAN.md`, risk R6, makes
|
|
13
|
+
per-package `THIRD_PARTY_LICENSES.md` a release gate for npm publication.
|
|
14
|
+
|
|
15
|
+
Untangle itself is **AGPL-3.0-only** (see `LICENSE` and `NOTICE` in
|
|
16
|
+
this package). The packages below are third-party works; their own
|
|
17
|
+
licenses and copyright notices continue to apply unchanged. Workspace
|
|
18
|
+
packages (`@untangle-code/*`) are not listed here; they belong to the same
|
|
19
|
+
work and use the same AGPL-3.0-only license.
|
|
20
|
+
|
|
21
|
+
## Production dependencies (transitive, 1)
|
|
22
|
+
|
|
23
|
+
| Package | Version | License | Project |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| `smol-toml` | 1.8.0 | BSD-3-Clause | `github:squirrelchat/smol-toml` |
|
|
26
|
+
|
|
27
|
+
The allowlist (MIT / Apache-2.0 / BSD / ISC / …) is checked by
|
|
28
|
+
`scripts/check-licenses.mjs` against this exact tree on every run;
|
|
29
|
+
the GPL family is excluded (architecture.md §2.4, BUILD_PLAN R6).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chunk-token budget wiring: `IndexConfig` (spec.md §4.6) →
|
|
3
|
+
* `@untangle-code/chunker`'s `ChunkerConfigInput.maxTokensPerChunk`.
|
|
4
|
+
*
|
|
5
|
+
* spec.md §4.6 defines TWO index-level chunk-size knobs —
|
|
6
|
+
* `targetChunkTokens` (a soft aim) and `maxChunkTokens` (a hard ceiling) —
|
|
7
|
+
* with their product defaults (900 / 1500) fixed exactly once, in this
|
|
8
|
+
* package's `defaults.ts` (`DEFAULT_INDEX_CONFIG`). That is the single
|
|
9
|
+
* source of truth this file wires onward.
|
|
10
|
+
*
|
|
11
|
+
* `@untangle-code/chunker` currently exposes exactly ONE budget knob,
|
|
12
|
+
* `ChunkerConfigInput.maxTokensPerChunk`, and treats it everywhere as a hard
|
|
13
|
+
* "must not exceed" ceiling (every comparison in chunker's `split.ts` and
|
|
14
|
+
* `line-fallback-strategy.ts` is `<=`). The correct mapping is therefore
|
|
15
|
+
* `maxChunkTokens → maxTokensPerChunk`. `targetChunkTokens` has no
|
|
16
|
+
* chunker-side counterpart today — the chunker does not currently support
|
|
17
|
+
* aiming below its hard ceiling — and is deliberately NOT passed here;
|
|
18
|
+
* smuggling it in as `maxTokensPerChunk` instead would silently turn a soft
|
|
19
|
+
* aim into the hard ceiling the chunker actually enforces, which is a
|
|
20
|
+
* different (smaller) budget than spec.md §4.6 configures.
|
|
21
|
+
*
|
|
22
|
+
* `@untangle-code/config` deliberately does NOT take a runtime dependency on
|
|
23
|
+
* `@untangle-code/chunker` for this: config sits below chunker in the
|
|
24
|
+
* dependency graph (chunker already depends on domain; depending on chunker
|
|
25
|
+
* from here would invert that layering for the sake of one field mapping),
|
|
26
|
+
* and chunker is maintained independently of this package. `deriveChunkerBudget`
|
|
27
|
+
* therefore returns a plain object structurally compatible with
|
|
28
|
+
* `ChunkerConfigInput` (verified in `chunker-budget.test.ts` against the
|
|
29
|
+
* fields spec.md and the chunker README actually document) rather than
|
|
30
|
+
* importing its type.
|
|
31
|
+
*
|
|
32
|
+
* Whatever code builds the indexer (constructing `ChunkerConfigInput` to
|
|
33
|
+
* pass to `@untangle-code/chunker`'s `resolveChunkerConfig`/`chunkFile`) MUST
|
|
34
|
+
* spread this function's result into that input — e.g.
|
|
35
|
+
* `resolveChunkerConfig({ ...deriveChunkerBudget(effectiveConfig.index), preparedText })`
|
|
36
|
+
* — rather than reading `index.maxChunkTokens` directly or (worse) relying
|
|
37
|
+
* on the chunker's own independent default. That is the one piece of wiring
|
|
38
|
+
* this fix cannot complete by itself: no indexer package exists in this
|
|
39
|
+
* repository yet to receive it (see this package's `package.json`
|
|
40
|
+
* `dependencies`, and `packages/`'s directory listing) — but every future
|
|
41
|
+
* caller now has exactly one function to call instead of a field name to
|
|
42
|
+
* remember, which is what keeps a changed config value from silently
|
|
43
|
+
* failing to reach the chunker.
|
|
44
|
+
*/
|
|
45
|
+
import type { IndexConfig } from "./types.js";
|
|
46
|
+
/** Structurally assignable to `@untangle-code/chunker`'s `ChunkerConfigInput` — see file doc comment. */
|
|
47
|
+
export interface ChunkerBudgetInput {
|
|
48
|
+
maxTokensPerChunk: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Derives the chunker's token-budget input from the effective `IndexConfig`
|
|
52
|
+
* (spec.md §4.6). This is the ONLY function in the codebase permitted to
|
|
53
|
+
* decide which `IndexConfig` field feeds
|
|
54
|
+
* `ChunkerConfigInput.maxTokensPerChunk` — see file doc comment for why it
|
|
55
|
+
* is `maxChunkTokens`, not `targetChunkTokens`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function deriveChunkerBudget(index: Pick<IndexConfig, "maxChunkTokens">): ChunkerBudgetInput;
|
|
58
|
+
//# sourceMappingURL=chunker-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker-budget.d.ts","sourceRoot":"","sources":["../src/chunker-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,yGAAyG;AACzG,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,gBAAgB,CAAC,GACzC,kBAAkB,CAEpB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives the chunker's token-budget input from the effective `IndexConfig`
|
|
3
|
+
* (spec.md §4.6). This is the ONLY function in the codebase permitted to
|
|
4
|
+
* decide which `IndexConfig` field feeds
|
|
5
|
+
* `ChunkerConfigInput.maxTokensPerChunk` — see file doc comment for why it
|
|
6
|
+
* is `maxChunkTokens`, not `targetChunkTokens`.
|
|
7
|
+
*/
|
|
8
|
+
export function deriveChunkerBudget(index) {
|
|
9
|
+
return { maxTokensPerChunk: index.maxChunkTokens };
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=chunker-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker-budget.js","sourceRoot":"","sources":["../src/chunker-budget.ts"],"names":[],"mappings":"AAmDA;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAA0C;IAE1C,OAAO,EAAE,iBAAiB,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ChangeConfig, ComplexityConfig, EmbeddingConfig, GraphConfig, IndexConfig, PortfolioConfig, QdrantConfig, QualityGateConfig, RawConfigLayer, SearchConfig, StorageConfig, TelemetryConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Embedding profile fields that are NOT part of `EmbeddingConfig` (spec.md
|
|
4
|
+
* §4.5 lists exactly ten fields, none of them `normalization` or
|
|
5
|
+
* `preparedTextVersion`) but ARE required inputs to
|
|
6
|
+
* `buildEmbeddingProfileId` (spec.md §5.7). Untangle decision: default
|
|
7
|
+
* normalization to "none" (no vector post-processing) and mint an explicit
|
|
8
|
+
* prepared-text format version so a future change to how document text is
|
|
9
|
+
* assembled can roll `embedding_profile_id` forward deliberately.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_EMBEDDING_NORMALIZATION: "none";
|
|
12
|
+
export declare const PREPARED_TEXT_VERSION: "untangle-prepared-text-v1";
|
|
13
|
+
/** spec.md §4.5 "Defaultprofil für MVP" — Ollama, nomic-embed-text, 768 dims. */
|
|
14
|
+
export declare const DEFAULT_EMBEDDING_CONFIG: EmbeddingConfig;
|
|
15
|
+
/**
|
|
16
|
+
* Qdrant defaults. spec.md §4.4 defines the shape but not default values;
|
|
17
|
+
* `managed` mode with no explicit `url` matches product.md §4.1's
|
|
18
|
+
* zero-config narrative (the CLI starts a managed container and fills `url`
|
|
19
|
+
* in from that at runtime — this package only supplies the mode default).
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_QDRANT_CONFIG: QdrantConfig;
|
|
22
|
+
/** Embedded search for new projects; explicit legacy Qdrant layers retain their backend. */
|
|
23
|
+
export declare const DEFAULT_STORAGE_CONFIG: StorageConfig;
|
|
24
|
+
/** spec.md §4.6; numeric defaults are an Untangle decision, chosen conservatively. */
|
|
25
|
+
export declare const DEFAULT_INDEX_CONFIG: IndexConfig;
|
|
26
|
+
/** Untangle decision (see `types.ts` `SearchConfig` doc comment). */
|
|
27
|
+
export declare const DEFAULT_SEARCH_CONFIG: SearchConfig;
|
|
28
|
+
/** Untangle decision, values taken from architecture.md §17.4's per-query limits. */
|
|
29
|
+
export declare const DEFAULT_GRAPH_CONFIG: GraphConfig;
|
|
30
|
+
/** Untangle decision (see `types.ts` `ComplexityConfig` doc comment). */
|
|
31
|
+
export declare const DEFAULT_COMPLEXITY_CONFIG: ComplexityConfig;
|
|
32
|
+
/** spec.md §17.6 default risk score weights. */
|
|
33
|
+
export declare const DEFAULT_CHANGE_CONFIG: ChangeConfig;
|
|
34
|
+
/** spec.md §4.7 shape; MVP defaults chosen deliberately permissive (warn, not fail). */
|
|
35
|
+
export declare const DEFAULT_QUALITY_GATE_CONFIG: QualityGateConfig;
|
|
36
|
+
/** spec.md §24: "Telemetrie MUSS standardmäßig aus sein." */
|
|
37
|
+
export declare const DEFAULT_TELEMETRY_CONFIG: TelemetryConfig;
|
|
38
|
+
/** portfolio.md §14.4 defaults, spelled out explicitly. */
|
|
39
|
+
export declare const DEFAULT_PORTFOLIO_CONFIG: PortfolioConfig;
|
|
40
|
+
/** Current config schema version this package produces and validates against. */
|
|
41
|
+
export declare const CURRENT_SCHEMA_VERSION: 1;
|
|
42
|
+
/**
|
|
43
|
+
* The complete product-default layer (spec.md §4.1 layer 6, lowest
|
|
44
|
+
* priority). Every optional sub-config `EffectiveUntangleConfigV1`
|
|
45
|
+
* guarantees present is defaulted here; `repositories` and
|
|
46
|
+
* `contextArtifacts` are deliberately absent (spec.md §4.3: no universal
|
|
47
|
+
* default repository set exists).
|
|
48
|
+
*/
|
|
49
|
+
export declare const PRODUCT_DEFAULT_CONFIG: RawConfigLayer;
|
|
50
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,WAAW,EACX,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;GAQG;AACH,eAAO,MAAM,+BAA+B,EAAG,MAAe,CAAC;AAC/D,eAAO,MAAM,qBAAqB,EAAG,2BAAoC,CAAC;AAE1E,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,EAAE,eAYtC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAOnC,CAAC;AAEF,4FAA4F;AAC5F,eAAO,MAAM,sBAAsB,EAAE,aAGpC,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,oBAAoB,EAAE,WAelC,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,EAAE,YAanC,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,oBAAoB,EAAE,WAOlC,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,yBAAyB,EAAE,gBAKvC,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,qBAAqB,EAAE,YAenC,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,2BAA2B,EAAE,iBAKzC,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,wBAAwB,EAAE,eAItC,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,EAAE,eAuBtC,CAAC;AAEF,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,EAAG,CAAU,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAapC,CAAC"}
|