Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ADOPTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ What you do **not** copy: HANDBOOK.md, the `methodology` domain, `examples/`, `t
curl -fsSL https://raw.githubusercontent.com/emafriedrich/knowledge-driven-engineering/main/install.sh | bash -s -- <your-first-domain>
```

`install.sh` performs the manual steps below, is idempotent, and never overwrites an existing file (it skips and tells you). It detects your package manager (npm, pnpm — workspaces included, yarn, bun) for the `yaml` dependency, and a dependency failure warns instead of aborting the install. Run it from the root of your repository; pass your first domain name as the argument. The argument only seeds a fresh install: on a repository that already has `knowledge/index.yaml` the installer creates no domain and points you to `npm run knowledge -- domain add <name>`. Installing, upgrading (`--upgrade`) and adding a domain are three separate actions. Offline installs work from a local clone: `KDE_SOURCE=/path/to/clone bash install.sh <domain>`. To pin a release instead of tracking `main`, set `KDE_REF=v0.1.0`.
`install.sh` performs the manual steps below, is idempotent, and never overwrites an existing file (it skips and tells you). It detects your package manager (npm, pnpm — workspaces included, yarn, bun) for the `yaml` dependency, and a dependency failure warns instead of aborting the install. Run it from the root of your repository; pass your first domain name as the argument. It refuses to run in a subdirectory of a repository that already runs KDE — a second catalog nothing reads — and names the directory to run it from. The argument only seeds a fresh install: on a repository that already has `knowledge/index.yaml` the installer creates no domain and points you to `npm run knowledge -- domain add <name>`. Installing, upgrading (`--upgrade`) and adding a domain are three separate actions. Offline installs work from a local clone: `KDE_SOURCE=/path/to/clone bash install.sh <domain>`. To pin a release instead of tracking `main`, set `KDE_REF=v0.1.0`.

## Upgrading

Expand Down
22 changes: 21 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ say() { printf '%s\n' "$*"; }
add() { say " add $1"; }
skip() { say " skip $1 (already exists)"; }

# A repository runs one knowledge catalog. Installing below a directory of the
# same git repository that already has one creates a second, empty KDE that
# nothing reads — refuse and name the right directory instead.
TOPLEVEL="$(git rev-parse --show-toplevel 2>/dev/null || true)"
if [ -n "$TOPLEVEL" ]; then
HERE="$(pwd -P)"
TOPLEVEL="$(cd "$TOPLEVEL" && pwd -P)"
dir="$HERE"
while [ "$dir" != "$TOPLEVEL" ] && [ "$dir" != "/" ]; do
dir="$(dirname "$dir")"
if [ -f "$dir/knowledge/index.yaml" ] && [ -f "$dir/tools/knowledge-check.mts" ]; then
say "install.sh: ${dir} already runs Knowledge-Driven Engineering; run the installer from there, not from ${HERE}." >&2
exit 1
fi
done
fi

# Version of the framework-owned files a repository runs, read from the
# kde-version marker the installer stamps on line 1 of every copy.
installed_version() {
Expand Down Expand Up @@ -403,7 +420,10 @@ fi
say ""
say "Done. Next steps:"
say " 1. npm run knowledge:check"
say " 2. Seed current truth with one decision your team already made:"
say " 2. Existing codebase? Recover the behavior that lives only in code, one domain at a time:"
say " npm run knowledge -- backfill ${DOMAIN:-<domain>}"
say " then ask your coding agent to \"backfill the ${DOMAIN:-<domain>} domain\" — it follows tools/backfill-protocol.md."
say " Starting fresh? Seed current truth with one decision your team already made:"
say " npm run knowledge -- new decision ${DOMAIN:-<domain>} \"<title>\" --author <you>"
say " npm run knowledge -- promote <ID> --by <you>"
say " 3. Enable branch protection with code-owner review so knowledge promotion needs a human."
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "knowledge-driven-engineering",
"version": "0.7.1",
"version": "0.7.2",
"private": true,
"type": "module",
"scripts": {
Expand Down
20 changes: 19 additions & 1 deletion tests/install.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { appendFileSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import test from 'node:test';
Expand Down Expand Up @@ -83,6 +83,24 @@ test('installer stamps framework-owned files with the package.json version and r
}
});

test('installer refuses a subdirectory of a repository that already runs KDE, and suggests backfill as a next step', () => {
const root = mkdtempSync(join(tmpdir(), 'kde-install-test-'));
try {
execFileSync('git', ['init', '-q'], { cwd: root });
const fresh = install(root, 'shop');
assert.match(fresh, /Existing codebase\? Recover the behavior that lives only in code[\s\S]*npm run knowledge -- backfill shop[\s\S]*"backfill the shop domain" — it follows tools\/backfill-protocol\.md/);
assert.match(fresh, /Starting fresh\? Seed current truth/);

const nested = join(root, 'apps', 'api');
mkdirSync(nested, { recursive: true });
assert.throws(() => install(nested, '--upgrade'), /already runs Knowledge-Driven Engineering; run the installer from there, not from .*apps\/api/);
assert.equal(existsSync(join(nested, 'knowledge')), false, 'nothing is written in the subdirectory');
assert.equal(existsSync(join(nested, 'tools')), false);
} finally {
rmSync(root, { recursive: true, force: true });
}
});

test('installer rejects unknown options', () => {
const root = mkdtempSync(join(tmpdir(), 'kde-install-test-'));
try {
Expand Down
6 changes: 6 additions & 0 deletions tests/knowledge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,12 @@ test('prompt: new scaffolds from the template and promote sets current — promp
});
});

test('a command run where the catalog has no domains says the directory may be the wrong one', () => {
withFixture({ 'knowledge/index.yaml': 'domains: {}\n' }, (root) => {
assert.throws(() => commandBackfill(root, 'orders'), /has no domains at all — if this repository runs KDE from another directory, run the command there/);
});
});

test('cli: usage errors are CommandErrors, and flags with several values are collected', () => {
withFixture({}, (root) => {
assert.throws(() => run(root, ['promote']), /usage:/);
Expand Down
6 changes: 5 additions & 1 deletion tools/knowledge.mts
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,14 @@ function slugify(text: string): string {
// --- Repository lookups -----------------------------------------------------

function findDomain(root: string, name: string): { catalog: Catalog; domain: CatalogDomain } {
for (const catalog of loadCatalogs(root)) {
const catalogs = loadCatalogs(root);
for (const catalog of catalogs) {
const domain = catalog.domains.get(name);
if (domain) return { catalog, domain };
}
if (catalogs.every((catalog) => catalog.domains.size === 0)) {
throw new CommandError(`domain ${name} is not in any catalog, and the catalog in ${root} has no domains at all — if this repository runs KDE from another directory, run the command there; otherwise create it: npm run knowledge -- domain add ${name} --description "<one line>"`);
}
throw new CommandError(`domain ${name} is not in any catalog — create it: npm run knowledge -- domain add ${name} --description "<one line>"`);
}

Expand Down
Loading