@buba_71/levit 0.3.3 → 0.4.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/README.md +14 -1
- package/dist/bin/cli.js +38 -14
- package/dist/src/commands/decision.js +59 -0
- package/dist/src/commands/feature.js +61 -0
- package/dist/src/commands/handoff.js +49 -0
- package/dist/src/commands/index.js +11 -0
- package/dist/src/commands/init.js +56 -0
- package/dist/src/core/cli_args.js +35 -0
- package/dist/src/core/ids.js +34 -0
- package/dist/src/core/levit_project.js +30 -0
- package/dist/src/core/paths.js +29 -0
- package/dist/src/core/version.js +22 -0
- package/dist/src/core/write_file.js +15 -0
- package/dist/src/init.js +12 -40
- package/dist/tests/init.test.js +93 -1
- package/package.json +1 -1
- package/templates/default/.levit/AGENT_ONBOARDING.md +28 -0
- package/templates/default/.levit/decision-record.md +19 -0
- package/templates/default/.levit/prompts/global-rules.md +10 -0
- package/templates/default/.levit/prompts/refactoring-guidelines.md +9 -0
- package/templates/default/.levit/workflows/example-task.md +9 -0
- package/templates/default/.levit/workflows/submit-for-review.md +18 -0
- package/templates/default/README.md +30 -11
- package/templates/default/SOCIAL_CONTRACT.md +5 -0
- package/templates/default/agents/AGENTS.md +7 -0
- package/templates/default/evals/README.md +14 -0
- package/templates/default/evals/conformance.eval.ts +16 -0
- package/templates/default/features/INTENT.md +23 -0
- package/templates/default/features/README.md +7 -8
- package/templates/default/package.json +11 -0
- package/templates/default/pipelines/README.md +2 -0
- package/templates/default/roles/README.md +13 -0
package/README.md
CHANGED
|
@@ -34,7 +34,8 @@ Its role is simple:
|
|
|
34
34
|
## What levit-kit does
|
|
35
35
|
|
|
36
36
|
- Initializes a standardized project structure
|
|
37
|
-
- Installs explicit conventions
|
|
37
|
+
- Installs explicit conventions for **AI-Driven Development (AIDD)**
|
|
38
|
+
- Provides a protocol for Human-AI collaboration (Intents, Decisions, Evals)
|
|
38
39
|
- Facilitates human and agent onboarding
|
|
39
40
|
- Reduces unnecessary variability between projects
|
|
40
41
|
|
|
@@ -78,6 +79,18 @@ Levit-kit does not remain in the project after initialization and installs no de
|
|
|
78
79
|
|
|
79
80
|
---
|
|
80
81
|
|
|
82
|
+
## The AIDD Workflow (Human + Agent)
|
|
83
|
+
|
|
84
|
+
Levit-kit installs a cognitive pipeline in your project:
|
|
85
|
+
|
|
86
|
+
1. **Human Intent**: You define *what* you want in `features/INTENT.md`.
|
|
87
|
+
2. **Agent Onboarding**: Your AI reads `.levit/AGENT_ONBOARDING.md` to learn your rules.
|
|
88
|
+
3. **Collaborative Decision**: The agent proposes technical choices in `.levit/decision-record.md`.
|
|
89
|
+
4. **Verification**: You or the agent run quality tests in `evals/`.
|
|
90
|
+
5. **Review**: The agent submits its work following a strict protocol.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
81
94
|
## Where does the levit command come from?
|
|
82
95
|
|
|
83
96
|
The `levit` command is provided through the npm ecosystem.
|
package/dist/bin/cli.js
CHANGED
|
@@ -4,38 +4,35 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const init_1 = require("../src/init");
|
|
7
|
+
const init_1 = require("../src/commands/init");
|
|
8
|
+
const decision_1 = require("../src/commands/decision");
|
|
9
|
+
const feature_1 = require("../src/commands/feature");
|
|
10
|
+
const handoff_1 = require("../src/commands/handoff");
|
|
11
|
+
const version_1 = require("../src/core/version");
|
|
8
12
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
-
function getVersion() {
|
|
11
|
-
try {
|
|
12
|
-
const packageJson = fs_extra_1.default.readJsonSync(node_path_1.default.join(__dirname, "..", "..", "package.json"));
|
|
13
|
-
return packageJson.version;
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return "unknown";
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
13
|
function showHelp() {
|
|
20
14
|
console.log(`
|
|
21
15
|
Usage: levit [command] [options]
|
|
22
16
|
|
|
23
17
|
Commands:
|
|
24
18
|
init <project-name> Initialize a new levit project
|
|
19
|
+
feature new Create a new feature intent file (wizard)
|
|
20
|
+
decision new Create a new decision record (wizard)
|
|
21
|
+
handoff new Create an agent handoff brief (wizard)
|
|
25
22
|
|
|
26
23
|
Options:
|
|
27
24
|
-v, --version Show version number
|
|
28
25
|
-h, --help Show help
|
|
29
26
|
`);
|
|
30
27
|
}
|
|
31
|
-
function main() {
|
|
28
|
+
async function main() {
|
|
32
29
|
const args = process.argv.slice(2);
|
|
33
30
|
if (args.includes("-h") || args.includes("--help") || args.length === 0) {
|
|
34
31
|
showHelp();
|
|
35
32
|
process.exit(0);
|
|
36
33
|
}
|
|
37
34
|
if (args.includes("-v") || args.includes("--version")) {
|
|
38
|
-
console.log(`levit-kit v${getVersion()}`);
|
|
35
|
+
console.log(`levit-kit v${(0, version_1.getVersion)()}`);
|
|
39
36
|
process.exit(0);
|
|
40
37
|
}
|
|
41
38
|
if (args[0] === "init") {
|
|
@@ -59,10 +56,37 @@ function main() {
|
|
|
59
56
|
process.exit(1);
|
|
60
57
|
}
|
|
61
58
|
}
|
|
59
|
+
else if (args[0] === "feature") {
|
|
60
|
+
try {
|
|
61
|
+
await (0, feature_1.featureCommand)(args.slice(1), process.cwd());
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error(error instanceof Error ? `Error: ${error.message}` : "Unexpected error");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (args[0] === "decision") {
|
|
69
|
+
try {
|
|
70
|
+
await (0, decision_1.decisionCommand)(args.slice(1), process.cwd());
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error(error instanceof Error ? `Error: ${error.message}` : "Unexpected error");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (args[0] === "handoff") {
|
|
78
|
+
try {
|
|
79
|
+
await (0, handoff_1.handoffCommand)(args.slice(1), process.cwd());
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(error instanceof Error ? `Error: ${error.message}` : "Unexpected error");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
62
86
|
else {
|
|
63
87
|
console.error(`Error: Unknown command "${args[0]}"`);
|
|
64
88
|
showHelp();
|
|
65
89
|
process.exit(1);
|
|
66
90
|
}
|
|
67
91
|
}
|
|
68
|
-
main();
|
|
92
|
+
void main();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.decisionCommand = decisionCommand;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
9
|
+
const levit_project_1 = require("../core/levit_project");
|
|
10
|
+
const cli_args_1 = require("../core/cli_args");
|
|
11
|
+
const write_file_1 = require("../core/write_file");
|
|
12
|
+
const ids_1 = require("../core/ids");
|
|
13
|
+
function normalizeSlug(input) {
|
|
14
|
+
return input
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9\s-_]/g, "")
|
|
18
|
+
.replace(/\s+/g, "-");
|
|
19
|
+
}
|
|
20
|
+
async function decisionCommand(argv, cwd) {
|
|
21
|
+
const { positional, flags } = (0, cli_args_1.parseArgs)(argv);
|
|
22
|
+
const sub = positional[0];
|
|
23
|
+
if (sub !== "new") {
|
|
24
|
+
throw new Error('Usage: levit decision new [--title "..."] [--id "001"]');
|
|
25
|
+
}
|
|
26
|
+
const projectRoot = (0, levit_project_1.requireLevitProjectRoot)(cwd);
|
|
27
|
+
const yes = (0, cli_args_1.getBooleanFlag)(flags, "yes");
|
|
28
|
+
const overwrite = (0, cli_args_1.getBooleanFlag)(flags, "force");
|
|
29
|
+
let title = (0, cli_args_1.getStringFlag)(flags, "title");
|
|
30
|
+
let id = (0, cli_args_1.getStringFlag)(flags, "id");
|
|
31
|
+
const featureRef = (0, cli_args_1.getStringFlag)(flags, "feature");
|
|
32
|
+
const computedId = (0, ids_1.nextSequentialId)(node_path_1.default.join(projectRoot, ".levit", "decisions"), /^ADR-(\d+)-/);
|
|
33
|
+
if (!yes) {
|
|
34
|
+
const rl = promises_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
+
if (!title) {
|
|
36
|
+
title = (await rl.question("Decision title: ")).trim();
|
|
37
|
+
}
|
|
38
|
+
if (!id) {
|
|
39
|
+
id = (await rl.question(`Decision id [${computedId}]: `)).trim() || computedId;
|
|
40
|
+
}
|
|
41
|
+
await rl.close();
|
|
42
|
+
}
|
|
43
|
+
if (!id) {
|
|
44
|
+
id = computedId;
|
|
45
|
+
}
|
|
46
|
+
if (!title) {
|
|
47
|
+
throw new Error("Missing --title");
|
|
48
|
+
}
|
|
49
|
+
if (!id) {
|
|
50
|
+
throw new Error("Missing --id");
|
|
51
|
+
}
|
|
52
|
+
const slug = normalizeSlug(title);
|
|
53
|
+
const fileName = `ADR-${id}-${slug}.md`;
|
|
54
|
+
const decisionPath = node_path_1.default.join(projectRoot, ".levit", "decisions", fileName);
|
|
55
|
+
const featureLine = featureRef ? `- **Feature**: ${featureRef}\n` : "";
|
|
56
|
+
const content = `# ADR ${id}: ${title}\n\n- **Date**: [YYYY-MM-DD]\n- **Status**: [Draft / Proposed / Approved]\n${featureLine}\n## Context\n[fill]\n\n## Decision\n[fill]\n\n## Rationale\n[fill]\n\n## Alternatives Considered\n[fill]\n\n## Consequences\n[fill]\n`;
|
|
57
|
+
(0, write_file_1.writeTextFile)(decisionPath, content, { overwrite });
|
|
58
|
+
process.stdout.write(`Created ${node_path_1.default.relative(projectRoot, decisionPath)}\n`);
|
|
59
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.featureCommand = featureCommand;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
9
|
+
const levit_project_1 = require("../core/levit_project");
|
|
10
|
+
const cli_args_1 = require("../core/cli_args");
|
|
11
|
+
const write_file_1 = require("../core/write_file");
|
|
12
|
+
const ids_1 = require("../core/ids");
|
|
13
|
+
function normalizeSlug(input) {
|
|
14
|
+
return input
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9\s-_]/g, "")
|
|
18
|
+
.replace(/\s+/g, "-");
|
|
19
|
+
}
|
|
20
|
+
async function featureCommand(argv, cwd) {
|
|
21
|
+
const { positional, flags } = (0, cli_args_1.parseArgs)(argv);
|
|
22
|
+
const sub = positional[0];
|
|
23
|
+
if (sub !== "new") {
|
|
24
|
+
throw new Error('Usage: levit feature new [--title "..."] [--slug "..."] [--id "001"]');
|
|
25
|
+
}
|
|
26
|
+
const projectRoot = (0, levit_project_1.requireLevitProjectRoot)(cwd);
|
|
27
|
+
const yes = (0, cli_args_1.getBooleanFlag)(flags, "yes");
|
|
28
|
+
const overwrite = (0, cli_args_1.getBooleanFlag)(flags, "force");
|
|
29
|
+
let title = (0, cli_args_1.getStringFlag)(flags, "title");
|
|
30
|
+
let slug = (0, cli_args_1.getStringFlag)(flags, "slug");
|
|
31
|
+
let id = (0, cli_args_1.getStringFlag)(flags, "id");
|
|
32
|
+
const computedId = (0, ids_1.nextSequentialId)(node_path_1.default.join(projectRoot, "features"), /^(\d+)-/);
|
|
33
|
+
if (!yes) {
|
|
34
|
+
const rl = promises_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
35
|
+
if (!title) {
|
|
36
|
+
title = (await rl.question("Feature title: ")).trim();
|
|
37
|
+
}
|
|
38
|
+
if (!slug) {
|
|
39
|
+
const computed = normalizeSlug(title || "");
|
|
40
|
+
slug = (await rl.question(`Feature slug [${computed}]: `)).trim() || computed;
|
|
41
|
+
}
|
|
42
|
+
if (!id) {
|
|
43
|
+
id = (await rl.question(`Feature id [${computedId}]: `)).trim() || computedId;
|
|
44
|
+
}
|
|
45
|
+
await rl.close();
|
|
46
|
+
}
|
|
47
|
+
if (!id) {
|
|
48
|
+
id = computedId;
|
|
49
|
+
}
|
|
50
|
+
if (!title) {
|
|
51
|
+
throw new Error("Missing --title");
|
|
52
|
+
}
|
|
53
|
+
if (!slug) {
|
|
54
|
+
throw new Error("Missing --slug");
|
|
55
|
+
}
|
|
56
|
+
const fileName = `${id}-${slug}.md`;
|
|
57
|
+
const featurePath = node_path_1.default.join(projectRoot, "features", fileName);
|
|
58
|
+
const content = `# INTENT: ${title}\n\n## 1. Vision (The \"Why\")\n- **User Story**: [fill]\n- **Priority**: [Low / Medium / High / Critical]\n\n## 2. Success Criteria (The \"What\")\n- [ ] Criterion 1\n\n## 3. Boundaries (The \"No\")\n- Non-goal 1\n\n## 4. Technical Constraints\n- [fill]\n\n## 5. Agent Task\n- [fill]\n`;
|
|
59
|
+
(0, write_file_1.writeTextFile)(featurePath, content, { overwrite });
|
|
60
|
+
process.stdout.write(`Created ${node_path_1.default.relative(projectRoot, featurePath)}\n`);
|
|
61
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.handoffCommand = handoffCommand;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
9
|
+
const levit_project_1 = require("../core/levit_project");
|
|
10
|
+
const cli_args_1 = require("../core/cli_args");
|
|
11
|
+
const write_file_1 = require("../core/write_file");
|
|
12
|
+
function isoDate() {
|
|
13
|
+
return new Date().toISOString().slice(0, 10);
|
|
14
|
+
}
|
|
15
|
+
async function handoffCommand(argv, cwd) {
|
|
16
|
+
const { positional, flags } = (0, cli_args_1.parseArgs)(argv);
|
|
17
|
+
const sub = positional[0];
|
|
18
|
+
if (sub !== "new") {
|
|
19
|
+
throw new Error("Usage: levit handoff new --feature <features/001-...md> [--role developer|qa|security|devops]");
|
|
20
|
+
}
|
|
21
|
+
const projectRoot = (0, levit_project_1.requireLevitProjectRoot)(cwd);
|
|
22
|
+
const yes = (0, cli_args_1.getBooleanFlag)(flags, "yes");
|
|
23
|
+
const overwrite = (0, cli_args_1.getBooleanFlag)(flags, "force");
|
|
24
|
+
let feature = (0, cli_args_1.getStringFlag)(flags, "feature");
|
|
25
|
+
let role = (0, cli_args_1.getStringFlag)(flags, "role");
|
|
26
|
+
if (!yes) {
|
|
27
|
+
const rl = promises_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
+
if (!feature) {
|
|
29
|
+
feature = (await rl.question("Feature path (e.g. features/001-...md): ")).trim();
|
|
30
|
+
}
|
|
31
|
+
if (!role) {
|
|
32
|
+
role = (await rl.question("Role [developer]: ")).trim() || "developer";
|
|
33
|
+
}
|
|
34
|
+
await rl.close();
|
|
35
|
+
}
|
|
36
|
+
if (!feature) {
|
|
37
|
+
throw new Error("Missing --feature");
|
|
38
|
+
}
|
|
39
|
+
if (!role) {
|
|
40
|
+
role = "developer";
|
|
41
|
+
}
|
|
42
|
+
const safeRole = role.trim().toLowerCase();
|
|
43
|
+
const date = isoDate();
|
|
44
|
+
const fileName = `${date}-${node_path_1.default.basename(feature, node_path_1.default.extname(feature))}-${safeRole}.md`;
|
|
45
|
+
const handoffPath = node_path_1.default.join(projectRoot, ".levit", "handoff", fileName);
|
|
46
|
+
const content = `# Agent Handoff\n\n- **Date**: ${date}\n- **Role**: ${safeRole}\n- **Feature**: ${feature}\n\n## What to read first\n- SOCIAL_CONTRACT.md\n- .levit/AGENT_ONBOARDING.md\n- ${feature}\n\n## Boundaries\nFollow the Boundaries section of the feature spec strictly.\n\n## Deliverables\n- A minimal, atomic diff\n- A short summary: what changed + why\n- How to verify (commands to run)\n- Open questions / risks\n\n## Review protocol\nFollow: .levit/workflows/submit-for-review.md\n`;
|
|
47
|
+
(0, write_file_1.writeTextFile)(handoffPath, content, { overwrite });
|
|
48
|
+
process.stdout.write(`Created ${node_path_1.default.relative(projectRoot, handoffPath)}\n`);
|
|
49
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handoffCommand = exports.decisionCommand = exports.featureCommand = exports.initProject = void 0;
|
|
4
|
+
var init_1 = require("./init");
|
|
5
|
+
Object.defineProperty(exports, "initProject", { enumerable: true, get: function () { return init_1.initProject; } });
|
|
6
|
+
var feature_1 = require("./feature");
|
|
7
|
+
Object.defineProperty(exports, "featureCommand", { enumerable: true, get: function () { return feature_1.featureCommand; } });
|
|
8
|
+
var decision_1 = require("./decision");
|
|
9
|
+
Object.defineProperty(exports, "decisionCommand", { enumerable: true, get: function () { return decision_1.decisionCommand; } });
|
|
10
|
+
var handoff_1 = require("./handoff");
|
|
11
|
+
Object.defineProperty(exports, "handoffCommand", { enumerable: true, get: function () { return handoff_1.handoffCommand; } });
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initProject = initProject;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const paths_1 = require("../core/paths");
|
|
9
|
+
const version_1 = require("../core/version");
|
|
10
|
+
const init_1 = require("../init");
|
|
11
|
+
/**
|
|
12
|
+
* Initializes a new project by copying the default template.
|
|
13
|
+
*
|
|
14
|
+
* @param projectName The name of the project to create
|
|
15
|
+
* @param targetPath The absolute path where the project should be created
|
|
16
|
+
*/
|
|
17
|
+
function initProject(projectName, targetPath) {
|
|
18
|
+
const templatePath = (0, paths_1.getTemplatePath)("default");
|
|
19
|
+
const version = (0, version_1.getVersion)();
|
|
20
|
+
if (!projectName) {
|
|
21
|
+
throw new Error("Project name is required.");
|
|
22
|
+
}
|
|
23
|
+
// Ensure project name is valid for a directory
|
|
24
|
+
if (!/^[a-z0-9-_]+$/i.test(projectName)) {
|
|
25
|
+
throw new Error("Invalid project name. Use only letters, numbers, dashes, and underscores.");
|
|
26
|
+
}
|
|
27
|
+
if (fs_extra_1.default.existsSync(targetPath)) {
|
|
28
|
+
throw new Error(`Directory "${projectName}" already exists.`);
|
|
29
|
+
}
|
|
30
|
+
if (!fs_extra_1.default.existsSync(templatePath)) {
|
|
31
|
+
throw new Error(`Default template not found at ${templatePath}`);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
(0, init_1.generateProject)(targetPath, "default");
|
|
35
|
+
console.log("");
|
|
36
|
+
console.log(`🚀 levit-kit v${version}`);
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log(" ✔ Structure initialized");
|
|
39
|
+
console.log(" ✔ Conventions applied");
|
|
40
|
+
console.log("");
|
|
41
|
+
console.log(`✨ Project "${projectName}" is ready for development.`);
|
|
42
|
+
console.log("");
|
|
43
|
+
console.log("Next steps:");
|
|
44
|
+
console.log(` 1. cd ${projectName}`);
|
|
45
|
+
console.log(" 2. Read README.md for the Human Operator Guide");
|
|
46
|
+
console.log(" 3. Onboard your AI in .levit/AGENT_ONBOARDING.md");
|
|
47
|
+
console.log("");
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// Attempt clean up if directory was created but copy failed
|
|
51
|
+
if (fs_extra_1.default.existsSync(targetPath) && fs_extra_1.default.readdirSync(targetPath).length === 0) {
|
|
52
|
+
fs_extra_1.default.rmSync(targetPath, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseArgs = parseArgs;
|
|
4
|
+
exports.getStringFlag = getStringFlag;
|
|
5
|
+
exports.getBooleanFlag = getBooleanFlag;
|
|
6
|
+
function parseArgs(args) {
|
|
7
|
+
const positional = [];
|
|
8
|
+
const flags = {};
|
|
9
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
10
|
+
const token = args[i];
|
|
11
|
+
if (!token.startsWith("--")) {
|
|
12
|
+
positional.push(token);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const key = token.slice(2);
|
|
16
|
+
const next = args[i + 1];
|
|
17
|
+
if (!next || next.startsWith("--")) {
|
|
18
|
+
flags[key] = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
flags[key] = next;
|
|
22
|
+
i += 1;
|
|
23
|
+
}
|
|
24
|
+
return { positional, flags };
|
|
25
|
+
}
|
|
26
|
+
function getStringFlag(flags, key) {
|
|
27
|
+
const v = flags[key];
|
|
28
|
+
if (typeof v === "string") {
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
function getBooleanFlag(flags, key) {
|
|
34
|
+
return flags[key] === true;
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.nextSequentialId = nextSequentialId;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
/**
|
|
9
|
+
* Finds the next sequential 3-digit ID in a directory based on a filename pattern.
|
|
10
|
+
*
|
|
11
|
+
* @param directory The directory to scan
|
|
12
|
+
* @param pattern A regex with a single capture group for the ID number
|
|
13
|
+
* @returns The next ID as a zero-padded string (e.g., "001")
|
|
14
|
+
*/
|
|
15
|
+
function nextSequentialId(directory, pattern) {
|
|
16
|
+
if (!fs_extra_1.default.existsSync(directory)) {
|
|
17
|
+
return "001";
|
|
18
|
+
}
|
|
19
|
+
const files = fs_extra_1.default.readdirSync(directory);
|
|
20
|
+
let max = 0;
|
|
21
|
+
for (const f of files) {
|
|
22
|
+
const m = f.match(pattern);
|
|
23
|
+
if (!m) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const n = Number.parseInt(m[1], 10);
|
|
27
|
+
if (!Number.isFinite(n)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
max = Math.max(max, n);
|
|
31
|
+
}
|
|
32
|
+
const next = max + 1;
|
|
33
|
+
return String(next).padStart(3, "0");
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findLevitProjectRoot = findLevitProjectRoot;
|
|
7
|
+
exports.requireLevitProjectRoot = requireLevitProjectRoot;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
function findLevitProjectRoot(startDir) {
|
|
11
|
+
let current = node_path_1.default.resolve(startDir);
|
|
12
|
+
while (true) {
|
|
13
|
+
const marker = node_path_1.default.join(current, ".levit", "AGENT_ONBOARDING.md");
|
|
14
|
+
if (fs_extra_1.default.existsSync(marker)) {
|
|
15
|
+
return current;
|
|
16
|
+
}
|
|
17
|
+
const parent = node_path_1.default.dirname(current);
|
|
18
|
+
if (parent === current) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
current = parent;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function requireLevitProjectRoot(startDir) {
|
|
25
|
+
const root = findLevitProjectRoot(startDir);
|
|
26
|
+
if (!root) {
|
|
27
|
+
throw new Error("Not a levit project (missing .levit/AGENT_ONBOARDING.md). Run this command from a levit-initialized repository.");
|
|
28
|
+
}
|
|
29
|
+
return root;
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getPackageRoot = getPackageRoot;
|
|
7
|
+
exports.getTemplatesPath = getTemplatesPath;
|
|
8
|
+
exports.getTemplatePath = getTemplatePath;
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
/**
|
|
11
|
+
* Resolves the root directory of the levit-kit package.
|
|
12
|
+
* Assuming this file is at src/core/paths.ts,
|
|
13
|
+
* after compilation it will be at dist/src/core/paths.js.
|
|
14
|
+
*/
|
|
15
|
+
function getPackageRoot() {
|
|
16
|
+
return node_path_1.default.resolve(__dirname, "..", "..", "..");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns the absolute path to the templates directory.
|
|
20
|
+
*/
|
|
21
|
+
function getTemplatesPath() {
|
|
22
|
+
return node_path_1.default.join(getPackageRoot(), "templates");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the absolute path to a specific template.
|
|
26
|
+
*/
|
|
27
|
+
function getTemplatePath(templateName = "default") {
|
|
28
|
+
return node_path_1.default.join(getTemplatesPath(), templateName);
|
|
29
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getVersion = getVersion;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const paths_1 = require("./paths");
|
|
10
|
+
/**
|
|
11
|
+
* Reads and returns the version from the package.json.
|
|
12
|
+
*/
|
|
13
|
+
function getVersion() {
|
|
14
|
+
try {
|
|
15
|
+
const packageJsonPath = node_path_1.default.join((0, paths_1.getPackageRoot)(), "package.json");
|
|
16
|
+
const packageJson = fs_extra_1.default.readJsonSync(packageJsonPath);
|
|
17
|
+
return packageJson.version || "unknown";
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return "unknown";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeTextFile = writeTextFile;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
function writeTextFile(targetPath, content, options) {
|
|
10
|
+
fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(targetPath));
|
|
11
|
+
if (fs_extra_1.default.existsSync(targetPath) && !options.overwrite) {
|
|
12
|
+
throw new Error(`File already exists: ${targetPath}`);
|
|
13
|
+
}
|
|
14
|
+
fs_extra_1.default.writeFileSync(targetPath, content, "utf8");
|
|
15
|
+
}
|
package/dist/src/init.js
CHANGED
|
@@ -3,49 +3,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.generateProject = generateProject;
|
|
7
7
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
const packageJson = fs_extra_1.default.readJsonSync(node_path_1.default.join(getPackageRoot(), "package.json"));
|
|
17
|
-
return packageJson.version;
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return "0.0.0";
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function initProject(projectName, targetPath) {
|
|
24
|
-
const packageRoot = getPackageRoot();
|
|
25
|
-
const templatePath = node_path_1.default.join(packageRoot, "templates", "default");
|
|
26
|
-
const version = getVersion();
|
|
27
|
-
if (!projectName) {
|
|
28
|
-
throw new Error("Project name is required.");
|
|
29
|
-
}
|
|
30
|
-
if (fs_extra_1.default.existsSync(targetPath)) {
|
|
31
|
-
throw new Error(`Directory "${projectName}" already exists.`);
|
|
32
|
-
}
|
|
8
|
+
const paths_1 = require("./core/paths");
|
|
9
|
+
/**
|
|
10
|
+
* Core generation logic for a new project.
|
|
11
|
+
* Responsible only for file system operations.
|
|
12
|
+
*/
|
|
13
|
+
function generateProject(targetPath, templateName = "default") {
|
|
14
|
+
const templatePath = (0, paths_1.getTemplatePath)(templateName);
|
|
33
15
|
if (!fs_extra_1.default.existsSync(templatePath)) {
|
|
34
|
-
throw new Error(`
|
|
16
|
+
throw new Error(`Template "${templateName}" not found.`);
|
|
17
|
+
}
|
|
18
|
+
if (fs_extra_1.default.existsSync(targetPath) && fs_extra_1.default.readdirSync(targetPath).length > 0) {
|
|
19
|
+
throw new Error(`Target directory "${targetPath}" is not empty.`);
|
|
35
20
|
}
|
|
36
21
|
fs_extra_1.default.ensureDirSync(targetPath);
|
|
37
22
|
fs_extra_1.default.copySync(templatePath, targetPath);
|
|
38
|
-
console.log("");
|
|
39
|
-
console.log(`🚀 levit-kit v${version}`);
|
|
40
|
-
console.log("");
|
|
41
|
-
console.log(" ✔ Project directory created");
|
|
42
|
-
console.log(" ✔ Template copied");
|
|
43
|
-
console.log("");
|
|
44
|
-
console.log(`✨ Project "${projectName}" initialized successfully.`);
|
|
45
|
-
console.log("");
|
|
46
|
-
console.log("Next steps:");
|
|
47
|
-
console.log(` - cd ${projectName}`);
|
|
48
|
-
console.log(" - Read SOCIAL_CONTRACT.md");
|
|
49
|
-
console.log(" - Start defining features");
|
|
50
|
-
console.log("");
|
|
51
23
|
}
|
package/dist/tests/init.test.js
CHANGED
|
@@ -8,10 +8,14 @@ const node_assert_1 = __importDefault(require("node:assert"));
|
|
|
8
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
10
|
const node_os_1 = __importDefault(require("node:os"));
|
|
11
|
-
const
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
|
+
const init_1 = require("../src/commands/init");
|
|
12
13
|
function exists(p) {
|
|
13
14
|
return node_fs_1.default.existsSync(p);
|
|
14
15
|
}
|
|
16
|
+
function getCliPath() {
|
|
17
|
+
return node_path_1.default.join(process.cwd(), "dist", "bin", "cli.js");
|
|
18
|
+
}
|
|
15
19
|
(0, node_test_1.default)("levit init copies default template exactly", () => {
|
|
16
20
|
const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "levit-kit-test-"));
|
|
17
21
|
const projectName = "test-project";
|
|
@@ -26,8 +30,96 @@ function exists(p) {
|
|
|
26
30
|
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "roles")));
|
|
27
31
|
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "pipelines")));
|
|
28
32
|
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "docs")));
|
|
33
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "roles", "README.md")), "Roles README should exist");
|
|
34
|
+
// AIDD assertions
|
|
35
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit")), ".levit directory should exist");
|
|
36
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit", "AGENT_ONBOARDING.md")), "AGENT_ONBOARDING.md should exist");
|
|
37
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit", "decision-record.md")), "decision-record.md should exist");
|
|
38
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit", "workflows", "example-task.md")), "Example workflow should exist");
|
|
39
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit", "workflows", "submit-for-review.md")), "Submit for review workflow should exist");
|
|
40
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit", "prompts")), "Prompts directory should exist");
|
|
41
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit", "prompts", "global-rules.md")), "Global rules should exist");
|
|
42
|
+
// New folders assertions
|
|
43
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "evals")), "Evals directory should exist");
|
|
44
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "evals", "README.md")), "Evals README should exist");
|
|
45
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".gitignore")), ".gitignore should exist");
|
|
46
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "package.json")), "package.json should exist");
|
|
29
47
|
// Agent boundaries
|
|
30
48
|
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "agents", "AGENTS.md")), "Agent guidelines should exist");
|
|
31
49
|
// Feature contract
|
|
32
50
|
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "features", "README.md")), "Feature README should exist");
|
|
51
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "features", "INTENT.md")), "Feature INTENT template should exist");
|
|
52
|
+
// Clean up
|
|
53
|
+
node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
54
|
+
});
|
|
55
|
+
(0, node_test_1.default)("CLI --help works", () => {
|
|
56
|
+
const output = (0, node_child_process_1.execSync)("node dist/bin/cli.js --help").toString();
|
|
57
|
+
node_assert_1.default.ok(output.includes("Usage: levit [command] [options]"));
|
|
58
|
+
node_assert_1.default.ok(output.includes("init <project-name>"));
|
|
59
|
+
});
|
|
60
|
+
(0, node_test_1.default)("CLI feature new creates a feature intent file", () => {
|
|
61
|
+
const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "levit-kit-test-"));
|
|
62
|
+
const projectName = "test-project";
|
|
63
|
+
const projectPath = node_path_1.default.join(tempDir, projectName);
|
|
64
|
+
(0, init_1.initProject)(projectName, projectPath);
|
|
65
|
+
const cliPath = getCliPath();
|
|
66
|
+
(0, node_child_process_1.execSync)(`node ${cliPath} feature new --yes --id 001 --title "My Feature" --slug my-feature`, { cwd: projectPath });
|
|
67
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "features", "001-my-feature.md")));
|
|
68
|
+
node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
69
|
+
});
|
|
70
|
+
(0, node_test_1.default)("CLI feature new auto-assigns id when omitted", () => {
|
|
71
|
+
const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "levit-kit-test-"));
|
|
72
|
+
const projectName = "test-project";
|
|
73
|
+
const projectPath = node_path_1.default.join(tempDir, projectName);
|
|
74
|
+
(0, init_1.initProject)(projectName, projectPath);
|
|
75
|
+
const cliPath = getCliPath();
|
|
76
|
+
(0, node_child_process_1.execSync)(`node ${cliPath} feature new --yes --title "My Feature" --slug my-feature`, { cwd: projectPath });
|
|
77
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, "features", "001-my-feature.md")));
|
|
78
|
+
node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
79
|
+
});
|
|
80
|
+
(0, node_test_1.default)("CLI decision new creates a decision record", () => {
|
|
81
|
+
const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "levit-kit-test-"));
|
|
82
|
+
const projectName = "test-project";
|
|
83
|
+
const projectPath = node_path_1.default.join(tempDir, projectName);
|
|
84
|
+
(0, init_1.initProject)(projectName, projectPath);
|
|
85
|
+
const cliPath = getCliPath();
|
|
86
|
+
(0, node_child_process_1.execSync)(`node ${cliPath} decision new --yes --id 001 --title "Choose DB" --feature features/001-some-feature.md`, { cwd: projectPath });
|
|
87
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit", "decisions")));
|
|
88
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit", "decisions", "ADR-001-choose-db.md")));
|
|
89
|
+
node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
90
|
+
});
|
|
91
|
+
(0, node_test_1.default)("CLI decision new auto-assigns id when omitted", () => {
|
|
92
|
+
const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "levit-kit-test-"));
|
|
93
|
+
const projectName = "test-project";
|
|
94
|
+
const projectPath = node_path_1.default.join(tempDir, projectName);
|
|
95
|
+
(0, init_1.initProject)(projectName, projectPath);
|
|
96
|
+
const cliPath = getCliPath();
|
|
97
|
+
(0, node_child_process_1.execSync)(`node ${cliPath} decision new --yes --title "Auto ID Decision"`, { cwd: projectPath });
|
|
98
|
+
node_assert_1.default.ok(exists(node_path_1.default.join(projectPath, ".levit", "decisions", "ADR-001-auto-id-decision.md")));
|
|
99
|
+
node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
100
|
+
});
|
|
101
|
+
(0, node_test_1.default)("CLI handoff new creates an agent handoff brief", () => {
|
|
102
|
+
const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "levit-kit-test-"));
|
|
103
|
+
const projectName = "test-project";
|
|
104
|
+
const projectPath = node_path_1.default.join(tempDir, projectName);
|
|
105
|
+
(0, init_1.initProject)(projectName, projectPath);
|
|
106
|
+
const cliPath = getCliPath();
|
|
107
|
+
(0, node_child_process_1.execSync)(`node ${cliPath} handoff new --yes --feature features/001-some-feature.md --role security`, { cwd: projectPath });
|
|
108
|
+
const handoffDir = node_path_1.default.join(projectPath, ".levit", "handoff");
|
|
109
|
+
node_assert_1.default.ok(exists(handoffDir));
|
|
110
|
+
node_assert_1.default.ok(node_fs_1.default.readdirSync(handoffDir).some((f) => f.endsWith("-security.md")));
|
|
111
|
+
node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
112
|
+
});
|
|
113
|
+
(0, node_test_1.default)("CLI --version works", () => {
|
|
114
|
+
const output = (0, node_child_process_1.execSync)("node dist/bin/cli.js --version").toString();
|
|
115
|
+
node_assert_1.default.ok(output.startsWith("levit-kit v"));
|
|
116
|
+
});
|
|
117
|
+
(0, node_test_1.default)("CLI name validation works", () => {
|
|
118
|
+
try {
|
|
119
|
+
(0, node_child_process_1.execSync)("node dist/bin/cli.js init invalid/name");
|
|
120
|
+
node_assert_1.default.fail("Should have failed with invalid name");
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
node_assert_1.default.ok(error.stderr.toString().includes("Error: Invalid project name"));
|
|
124
|
+
}
|
|
33
125
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# AGENT_ONBOARDING.md
|
|
2
|
+
|
|
3
|
+
> [!IMPORTANT]
|
|
4
|
+
> **To the AI Agent reading this**: You are entering a project governed by the **Levit-Kit** protocol. This document is your operating manual for this specific environment.
|
|
5
|
+
|
|
6
|
+
## 1. Project Essence
|
|
7
|
+
This project is a hybrid workspace where humans define intentions and AI ensures execution.
|
|
8
|
+
- **Human Role**: Vision, decision-making, final validation.
|
|
9
|
+
- **AI Role (You)**: Implementation, proactive suggestions, technical consistency.
|
|
10
|
+
|
|
11
|
+
## 2. Navigation & Context
|
|
12
|
+
To understand this project quickly, explore in this order:
|
|
13
|
+
1. `SOCIAL_CONTRACT.md`: Your ethical and operational boundaries.
|
|
14
|
+
2. `features/README.md`: The roadmap and logic of what we are building.
|
|
15
|
+
3. `agents/AGENTS.md`: Specific definitions of other roles if they exist.
|
|
16
|
+
4. `.levit/workflows/`: Step-by-step guides for common tasks.
|
|
17
|
+
|
|
18
|
+
## 3. Rules of Engagement
|
|
19
|
+
- **Transparency**: If you are unsure about a decision, ask the human. Do not "hallucinate" architectural choices.
|
|
20
|
+
- **Atomicity**: Keep your edits small and focused.
|
|
21
|
+
- **Traceability**: Every major change should be linked to a feature defined in `features/`.
|
|
22
|
+
- **Tests First**: Before submitting a complex change, check if tests exist in `tests/` and run them.
|
|
23
|
+
|
|
24
|
+
## 4. How to use Workflows
|
|
25
|
+
Check `.levit/workflows/` before starting a task. If a workflow like `add-logic.md` exists, follow its steps strictly to maintain project harmony.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
*Generated by Levit-Kit - Elevating Human-AI Collaboration.*
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Decision Record: [Title of the Decision]
|
|
2
|
+
|
|
3
|
+
- **Date**: [YYYY-MM-DD]
|
|
4
|
+
- **Status**: [Draft / Proposed / Approved]
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
What is the problem we are solving? What are the constraints?
|
|
8
|
+
|
|
9
|
+
## Decision
|
|
10
|
+
What is the chosen solution? How does it solve the problem?
|
|
11
|
+
|
|
12
|
+
## Rationale
|
|
13
|
+
Why did we choose this? (e.g., performance, simplicity, maintainability, cost)
|
|
14
|
+
|
|
15
|
+
## Alternatives Considered
|
|
16
|
+
What was rejected and why?
|
|
17
|
+
|
|
18
|
+
## Consequences
|
|
19
|
+
What are the trade-offs? What are the next steps?
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Project Standards & Global Rules
|
|
2
|
+
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> These rules are defaults provided by Levit-Kit. Feel free to adapt them to your specific tech stack.
|
|
5
|
+
|
|
6
|
+
1. **Strict Typing**: Prioritize strong typing and explicit interfaces over dynamic types.
|
|
7
|
+
2. **Documentation**: Every core function or business logic should include descriptive comments.
|
|
8
|
+
3. **Atomicity**: One task = one intent = one atomic implementation.
|
|
9
|
+
4. **No Hallucinations**: Do not assume the existence of external dependencies. Verify the environment before using a library.
|
|
10
|
+
5. **Quality First**: Follow the project's architectural patterns (SOLID, modularity).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Refactoring Guidelines
|
|
2
|
+
|
|
3
|
+
Follow these steps when task involves cleaning or refactoring existing code.
|
|
4
|
+
|
|
5
|
+
1. **Analyze**: Run tests before changing anything.
|
|
6
|
+
2. **Protect**: Identify the critical path of the logic.
|
|
7
|
+
3. **Iterate**: Make small, verifiable changes.
|
|
8
|
+
4. **Verify**: Re-run tests and run the `conformance.eval.ts`.
|
|
9
|
+
5. **Document**: Update `SOCIAL_CONTRACT.md` if the refactoring changes a core principle.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Workflow: Submit for Review
|
|
2
|
+
|
|
3
|
+
When you have finished a task, follow these steps to hand over to the Human:
|
|
4
|
+
|
|
5
|
+
1. **Self-Verification**:
|
|
6
|
+
- Ensure you haven't broken any principle from `SOCIAL_CONTRACT.md`.
|
|
7
|
+
- Verify that your code is atomic and follows naming conventions.
|
|
8
|
+
- Run existing tests and ensure they pass.
|
|
9
|
+
|
|
10
|
+
2. **The "Handover" Message**:
|
|
11
|
+
Provide a summary to the Human containing:
|
|
12
|
+
- **What changed**: A bullet-point list of modifications.
|
|
13
|
+
- **Verification**: How did you test it? (CLI output, test logs, etc.).
|
|
14
|
+
- **Next Steps**: What should the human do or check now?
|
|
15
|
+
- **Open Questions**: Are there any remaining uncertainties or technical debt?
|
|
16
|
+
|
|
17
|
+
3. **Status Update**:
|
|
18
|
+
Mark the task as completed in the relevant feature file or task tracker.
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
# Project Overview
|
|
2
2
|
|
|
3
|
-
This project was initialized with **
|
|
3
|
+
This project was initialized with **Levit-Kit**.
|
|
4
|
+
It is designed for a **hybrid workspace** where Human vision leads and AI execution follows.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
for working with humans and AI agents.
|
|
6
|
+
## 🚀 Quick Start: Human Operator Guide
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- makes no automated decisions
|
|
8
|
+
### 1. The Day One Setup
|
|
9
|
+
- **Adjust Governance**: Open `SOCIAL_CONTRACT.md` and tweak the principles to match your vision.
|
|
10
|
+
- **Define Standards**: Open `.levit/prompts/global-rules.md` to set your technical expectations (language, styling, strictness).
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
### 2. The "Intent-First" Workflow
|
|
13
|
+
When building a new feature, do not start with code:
|
|
14
|
+
1. **Declare Intent**: Copy `features/INTENT.md` to a new file (e.g., `features/001-my-feature.md`).
|
|
15
|
+
2. **Define Boundaries**: Fill in the "User Story" and especially the "Boundaries" section (what the AI must NOT touch).
|
|
16
|
+
|
|
17
|
+
### 3. Leading your Agents
|
|
18
|
+
When using an AI agent (Antigravity, Cursor, etc.):
|
|
19
|
+
1. **Onboard the Agent**: Direct it to read `.levit/AGENT_ONBOARDING.md` in your first prompt.
|
|
20
|
+
2. **Assign the Task**: Point it to your new intent file in `features/`.
|
|
21
|
+
3. **Review the Output**: Follow the guides in `.levit/workflows/submit-for-review.md`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🏛️ Project Principles
|
|
26
|
+
This repository is built for **clarity over automation**:
|
|
27
|
+
- **Explicit Structure**: No hidden magic.
|
|
28
|
+
- **Human Sovereignty**: You make the final decisions.
|
|
29
|
+
- **Traceability**: All technical choices are documented (see `.levit/decision-record.md`).
|
|
30
|
+
|
|
31
|
+
## 📂 Navigation
|
|
32
|
+
- `SOCIAL_CONTRACT.md`: Your ethical and operational foundations.
|
|
33
|
+
- `.levit/`: The AI's workspace (Onboarding, Prompts, Workflows).
|
|
34
|
+
- `features/`: The project roadmap and active intents.
|
|
35
|
+
- `evals/`: Technical quality tests for AI outputs.
|
|
@@ -8,6 +8,11 @@ This project follows the levit-kit social contract.
|
|
|
8
8
|
- Agents are assistants, not decision-makers
|
|
9
9
|
- Structure is explicit
|
|
10
10
|
- Conventions are preferred over configuration
|
|
11
|
+
- **Agents (AI) are onboarded through specific protocols**
|
|
12
|
+
|
|
13
|
+
## Agent (AI) Role
|
|
14
|
+
|
|
15
|
+
This project is AI-friendly. Agents must follow the instructions located in `.levit/AGENT_ONBOARDING.md`.
|
|
11
16
|
|
|
12
17
|
## What this project does not do
|
|
13
18
|
|
|
@@ -10,4 +10,11 @@ Agents are not allowed to:
|
|
|
10
10
|
- restructure the repository
|
|
11
11
|
- introduce implicit behavior
|
|
12
12
|
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Related Resources
|
|
16
|
+
|
|
17
|
+
- [Project Roles](../roles/README.md): Understand the specific responsibilities within this project.
|
|
18
|
+
- [Feature Contracts](../features/README.md): High-level intents and constraints for new developments.
|
|
19
|
+
|
|
13
20
|
When unsure, agents must ask for human validation.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Evaluations (Evals)
|
|
2
|
+
|
|
3
|
+
This directory is dedicated to testing the **intentions** and the **quality** of the AI output, rather than just the code implementation.
|
|
4
|
+
|
|
5
|
+
## Why Evals?
|
|
6
|
+
While unit tests verify if the code works (logic), Evals verify if the AI respected the "Social Contract" and the "Human Intent".
|
|
7
|
+
|
|
8
|
+
## Example Evals
|
|
9
|
+
- **Conformance**: Does the generated code follow our naming conventions?
|
|
10
|
+
- **Security**: Did the AI introduce any obvious secret leaks in the prompts?
|
|
11
|
+
- **Compliance**: Does the output still match the rules in `.levit/AGENT_ONBOARDING.md`?
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
*Verify then Trust.*
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example Conformance Evaluation
|
|
3
|
+
*
|
|
4
|
+
* This is a placeholder for an evaluation script.
|
|
5
|
+
* In a real-world scenario, you would use a library like Promptfoo or Giskard
|
|
6
|
+
* to run these evaluations against your agent's outputs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const conformanceEval = {
|
|
10
|
+
name: "Social Contract Adherence",
|
|
11
|
+
criteria: [
|
|
12
|
+
"Does the code follow camelCase for variables?",
|
|
13
|
+
"Is every change linked to an entry in features/?",
|
|
14
|
+
"Does the AI avoid modifying files outside its boundaries?"
|
|
15
|
+
]
|
|
16
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# INTENT: [Feature Name]
|
|
2
|
+
|
|
3
|
+
> Copy this file to create a new feature specification.
|
|
4
|
+
|
|
5
|
+
## 1. Vision (The "Why")
|
|
6
|
+
- **User Story**: As a [role], I want to [action], so that [value].
|
|
7
|
+
- **Priority**: [Low / Medium / High / Critical]
|
|
8
|
+
|
|
9
|
+
## 2. Success Criteria (The "What")
|
|
10
|
+
What must be true for this feature to be considered "Done"?
|
|
11
|
+
- [ ] Criterion 1
|
|
12
|
+
- [ ] Criterion 2
|
|
13
|
+
|
|
14
|
+
## 3. Boundaries (The "No")
|
|
15
|
+
What is explicitly **OUT OF SCOPE** for this iteration? Prevents scope creep.
|
|
16
|
+
- Non-goal 1
|
|
17
|
+
- Non-goal 2
|
|
18
|
+
|
|
19
|
+
## 4. Technical Constraints
|
|
20
|
+
- Specific tech, rules, or patterns to follow.
|
|
21
|
+
|
|
22
|
+
## 5. Agent Task
|
|
23
|
+
Specific starting point for the AI (e.g., "Implement the API endpoint first").
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# Features
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This directory contains the roadmap and the active specifications of the project.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- constraints
|
|
5
|
+
## How to add a feature
|
|
6
|
+
1. Copy [INTENT.md](./INTENT.md) to a new file (e.g., `001-my-feature.md`).
|
|
7
|
+
2. Fill the "Human Intent" sections.
|
|
8
|
+
3. Let the Agent implement the technical details.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
---
|
|
11
|
+
📄 Refer to [.levit/AGENT_ONBOARDING.md](../.levit/AGENT_ONBOARDING.md) for collaboration guidelines.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Project Roles
|
|
2
|
+
|
|
3
|
+
This directory contains the definitions of human and agent roles within this project.
|
|
4
|
+
|
|
5
|
+
## Available Roles
|
|
6
|
+
|
|
7
|
+
- [DevOps](devops.md): Infrastructure, deployment, and operational requirements.
|
|
8
|
+
- [QA](qa.md): Quality standards, testing expectations, and validation criteria.
|
|
9
|
+
- [Security](security.md): Threat assumptions and security constraints.
|
|
10
|
+
|
|
11
|
+
## Goal
|
|
12
|
+
|
|
13
|
+
The goal of defining roles is to **reduce ambiguity**. Each role document should clearly state its boundaries and responsibilities to facilitate collaboration between humans and AI agents.
|