gaslighting-engine 0.2.2 → 0.3.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.
Files changed (36) hide show
  1. package/.agents/prompts/gaslighting.md +2 -1
  2. package/.agents/skills/gaslighting/SKILL.md +4 -1
  3. package/.codex/prompts/gaslighting.md +2 -1
  4. package/.codex/skills/gaslighting/SKILL.md +4 -1
  5. package/README.md +36 -1
  6. package/dist/cli.js +38 -1
  7. package/dist/commands/cockpit.js +174 -0
  8. package/dist/commands/doctor.js +1 -0
  9. package/dist/core/cockpitHtml.js +596 -0
  10. package/dist/core/codexRuntime.js +74 -0
  11. package/dist/core/generateDocs.js +5 -3
  12. package/dist/core/generateOtherMarkdown.js +68 -4
  13. package/dist/index.js +1 -1
  14. package/dist/utils/logger.js +1 -1
  15. package/dist/version.js +1 -1
  16. package/docs/codex-usage.md +16 -4
  17. package/docs/examples.md +3 -0
  18. package/examples/ecommerce/.codex/prompts/gaslighting.md +2 -1
  19. package/examples/ecommerce/.codex/skills/gaslighting/SKILL.md +4 -1
  20. package/examples/ecommerce/.gaslighting/AGENTS.md +3 -1
  21. package/examples/ecommerce/.gaslighting/AGENT_RUNTIME.md +54 -0
  22. package/examples/ecommerce/.gaslighting/CODEX_PROMPT.md +2 -1
  23. package/examples/ecommerce/AGENTS.md +3 -2
  24. package/examples/hospital-homepage/.codex/prompts/gaslighting.md +2 -1
  25. package/examples/hospital-homepage/.codex/skills/gaslighting/SKILL.md +4 -1
  26. package/examples/hospital-homepage/.gaslighting/AGENTS.md +3 -1
  27. package/examples/hospital-homepage/.gaslighting/AGENT_RUNTIME.md +54 -0
  28. package/examples/hospital-homepage/.gaslighting/CODEX_PROMPT.md +2 -1
  29. package/examples/hospital-homepage/AGENTS.md +3 -2
  30. package/examples/landing-page/.codex/prompts/gaslighting.md +2 -1
  31. package/examples/landing-page/.codex/skills/gaslighting/SKILL.md +4 -1
  32. package/examples/landing-page/.gaslighting/AGENTS.md +3 -1
  33. package/examples/landing-page/.gaslighting/AGENT_RUNTIME.md +54 -0
  34. package/examples/landing-page/.gaslighting/CODEX_PROMPT.md +2 -1
  35. package/examples/landing-page/AGENTS.md +3 -2
  36. package/package.json +1 -1
@@ -10,7 +10,8 @@ Read the Gaslighting-engine project-control files before doing any work:
10
10
  6. `.gaslighting/DECISION_LOG.md`
11
11
  7. `AGENTS.md`
12
12
  8. `.gaslighting/MEMORY.md`
13
- 9. `.gaslighting/PROJECT_CARE.md`
13
+ 9. `.gaslighting/AGENT_RUNTIME.md`
14
+ 10. `.gaslighting/PROJECT_CARE.md`
14
15
 
15
16
  Then execute the user's requested implementation.
16
17
 
@@ -23,7 +23,8 @@ Instead:
23
23
  10. Generate `.gaslighting/DECISION_LOG.md`.
24
24
  11. Generate `.gaslighting/STACK_POLICY.md`.
25
25
  12. Generate or update root `AGENTS.md`.
26
- 13. Generate `.gaslighting/PROJECT_CARE.md`.
26
+ 13. Generate `.gaslighting/AGENT_RUNTIME.md`.
27
+ 14. Generate `.gaslighting/PROJECT_CARE.md`.
27
28
 
28
29
  ## Hardcore Discipline Rule
29
30
 
@@ -92,6 +93,8 @@ Keep main project-control files in `.gaslighting/` and keep only the Codex point
92
93
 
93
94
  Use `.gaslighting/PROJECT_CARE.md` to track Git/GitHub/domain/deployment/launch risks without blocking implementation.
94
95
 
96
+ Use `.gaslighting/AGENT_RUNTIME.md` to define the execution loop Codex must follow after the docs are generated.
97
+
95
98
  Do not only explain.
96
99
 
97
100
  Do not produce a plan without files.
@@ -10,7 +10,8 @@ Read the Gaslighting-engine project-control files before doing any work:
10
10
  6. `.gaslighting/DECISION_LOG.md`
11
11
  7. `AGENTS.md`
12
12
  8. `.gaslighting/MEMORY.md`
13
- 9. `.gaslighting/PROJECT_CARE.md`
13
+ 9. `.gaslighting/AGENT_RUNTIME.md`
14
+ 10. `.gaslighting/PROJECT_CARE.md`
14
15
 
15
16
  Then execute the user's requested implementation.
16
17
 
@@ -23,7 +23,8 @@ Instead:
23
23
  10. Generate `.gaslighting/DECISION_LOG.md`.
24
24
  11. Generate `.gaslighting/STACK_POLICY.md`.
25
25
  12. Generate or update root `AGENTS.md`.
26
- 13. Generate `.gaslighting/PROJECT_CARE.md`.
26
+ 13. Generate `.gaslighting/AGENT_RUNTIME.md`.
27
+ 14. Generate `.gaslighting/PROJECT_CARE.md`.
27
28
 
28
29
  ## Hardcore Discipline Rule
29
30
 
@@ -92,6 +93,8 @@ Keep main project-control files in `.gaslighting/` and keep only the Codex point
92
93
 
93
94
  Use `.gaslighting/PROJECT_CARE.md` to track Git/GitHub/domain/deployment/launch risks without blocking implementation.
94
95
 
96
+ Use `.gaslighting/AGENT_RUNTIME.md` to define the execution loop Codex must follow after the docs are generated.
97
+
95
98
  Do not only explain.
96
99
 
97
100
  Do not produce a plan without files.
package/README.md CHANGED
@@ -30,6 +30,7 @@ It generates:
30
30
  - `.gaslighting/MISSING_INFO.md`
31
31
  - `.gaslighting/DECISION_LOG.md`
32
32
  - `.gaslighting/MEMORY.md`
33
+ - `.gaslighting/AGENT_RUNTIME.md`
33
34
  - `.gaslighting/STACK_POLICY.md`
34
35
  - `.gaslighting/PROJECT_CARE.md`
35
36
  - `AGENTS.md`
@@ -40,9 +41,11 @@ It generates:
40
41
  ## Quick Start
41
42
 
42
43
  ```bash
43
- npx gaslighting-engine "I want to build a hospital website."
44
+ npx gaslighting-engine@latest start "I want to build a hospital website."
44
45
  ```
45
46
 
47
+ This generates the discipline documents, opens the local Mission Control GUI, and prepares a Codex launch command.
48
+
46
49
  Short aliases after global install or local link:
47
50
 
48
51
  ```bash
@@ -84,6 +87,9 @@ gaslighting init "I want to build a hospital website."
84
87
  gaslighting-engine generate "Build an ecommerce MVP."
85
88
  gaslighting-engine update "The hospital is actually an OB-GYN clinic, not dermatology."
86
89
  gaslighting-engine doctor
90
+ gaslighting-engine start "Build a hospital homepage"
91
+ gaslighting-engine cockpit
92
+ gaslighting-engine run "Build a hospital homepage"
87
93
  gaslighting-engine upgrade
88
94
  gaslighting-engine codex-install
89
95
  gaslighting-engine codex-doctor
@@ -99,6 +105,35 @@ Gaslighting-engine keeps project-control documents in `.gaslighting/` by default
99
105
 
100
106
  Only root `AGENTS.md` is written at the top level because Codex expects project guidance there. It is a thin pointer into `.gaslighting/`.
101
107
 
108
+ ## Mission Control
109
+
110
+ Mission Control is the Codex-first GUI agent surface:
111
+
112
+ ```bash
113
+ npx gaslighting-engine@latest start "I want to build a hospital website."
114
+ ```
115
+
116
+ It shows:
117
+
118
+ - project purpose
119
+ - generated control documents
120
+ - missing information
121
+ - Git/GitHub and deployment care checks
122
+ - Codex runtime args
123
+ - Start Codex action
124
+
125
+ Open it for an existing project:
126
+
127
+ ```bash
128
+ npx gaslighting-engine@latest cockpit
129
+ ```
130
+
131
+ Run without the GUI:
132
+
133
+ ```bash
134
+ npx gaslighting-engine@latest run "I want to build a hospital website."
135
+ ```
136
+
102
137
  ## Project Care
103
138
 
104
139
  Gaslighting-engine also writes `.gaslighting/PROJECT_CARE.md`.
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Command, InvalidArgumentError } from "commander";
2
2
  import { runAgents } from "./commands/agents.js";
3
3
  import { runCare } from "./commands/care.js";
4
+ import { runCockpit, runCodexRun, runStart } from "./commands/cockpit.js";
4
5
  import { runCodexDoctor, runCodexInstall } from "./commands/codexInstall.js";
5
6
  import { runDoctor } from "./commands/doctor.js";
6
7
  import { runGenerate } from "./commands/generate.js";
@@ -60,6 +61,7 @@ Examples:
60
61
  $ gaslighting-engine init "Build a landing page" --dry-run
61
62
  $ gaslighting-engine doctor
62
63
  $ gaslighting-engine care --github-url https://github.com/user/repo.git
64
+ $ gaslighting-engine start "Build a hospital homepage"
63
65
  $ gaslighting-engine upgrade
64
66
  $ gaslighting-engine codex-install --force
65
67
 
@@ -104,6 +106,41 @@ Defaults:
104
106
  .option("--github-url <url>", "initialize git if needed and connect origin to this GitHub URL")
105
107
  .option("--force-remote", "replace an existing origin remote when used with --github-url")
106
108
  .action(runCare);
109
+ program
110
+ .command("start <request>")
111
+ .description("Generate discipline docs, open Mission Control, and prepare Codex launch")
112
+ .option("--force", "overwrite existing files")
113
+ .option("--no-force", "preserve existing files and create .new variants instead")
114
+ .option("--no-open", "print the Mission Control URL without opening a browser")
115
+ .option("--port <port>", "Mission Control port, random by default")
116
+ .option("--path <path>", "target directory")
117
+ .option("--lang <lang>", "language: en or ko", "en")
118
+ .option("--type <type>", "project type override", parseProjectType)
119
+ .option("--codex-args <args>", "Codex CLI args used by the Start Codex button")
120
+ .action(runStart);
121
+ program
122
+ .command("cockpit [request]")
123
+ .description("Open the local Gaslighting Mission Control GUI")
124
+ .option("--no-generate", "open without regenerating discipline docs")
125
+ .option("--force", "overwrite existing files")
126
+ .option("--no-force", "preserve existing files and create .new variants instead")
127
+ .option("--no-open", "print the Mission Control URL without opening a browser")
128
+ .option("--port <port>", "Mission Control port, random by default")
129
+ .option("--path <path>", "target directory")
130
+ .option("--lang <lang>", "language: en or ko", "en")
131
+ .option("--type <type>", "project type override", parseProjectType)
132
+ .option("--codex-args <args>", "Codex CLI args used by the Start Codex button")
133
+ .action(runCockpit);
134
+ program
135
+ .command("run <request>")
136
+ .description("Generate discipline docs and launch Codex without opening the GUI")
137
+ .option("--force", "overwrite existing files")
138
+ .option("--no-force", "preserve existing files and create .new variants instead")
139
+ .option("--path <path>", "target directory")
140
+ .option("--lang <lang>", "language: en or ko", "en")
141
+ .option("--type <type>", "project type override", parseProjectType)
142
+ .option("--codex-args <args>", "Codex CLI args")
143
+ .action(runCodexRun);
107
144
  program
108
145
  .command("upgrade")
109
146
  .description("Update the local Codex Gaslighting install with one simple command")
@@ -134,7 +171,7 @@ export function normalizeDefaultInitArgv(argv) {
134
171
  return argv;
135
172
  if (args.some((arg) => ["--help", "-h", "--version", "-V"].includes(arg)))
136
173
  return argv;
137
- const commands = new Set(["init", "generate", "update", "doctor", "skill", "agents", "care", "upgrade", "codex-install", "codex-doctor"]);
174
+ const commands = new Set(["init", "generate", "update", "doctor", "skill", "agents", "care", "start", "cockpit", "run", "upgrade", "codex-install", "codex-doctor"]);
138
175
  const firstMeaningful = args.find((arg) => !arg.startsWith("-"));
139
176
  if (firstMeaningful && commands.has(firstMeaningful))
140
177
  return argv;
@@ -0,0 +1,174 @@
1
+ import { execFileSync, spawn } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { createServer } from "node:http";
4
+ import { join, resolve } from "node:path";
5
+ import { platform } from "node:os";
6
+ import { analyze } from "../core/analyze.js";
7
+ import { getDefaultCodexArgs, launchCodex } from "../core/codexRuntime.js";
8
+ import { pageList, projectPurpose } from "../core/content.js";
9
+ import { generateCodexInstallDocs, generateDocs, generateProjectCareDoc } from "../core/generateDocs.js";
10
+ import { renderCockpitHtml } from "../core/cockpitHtml.js";
11
+ import { connectGitHubRemote, inspectProjectCare } from "../core/projectCare.js";
12
+ import { printBanner } from "../utils/banner.js";
13
+ import { writeDocs } from "../utils/file.js";
14
+ import { packageVersion } from "../version.js";
15
+ const docFiles = [
16
+ "AGENTS.md",
17
+ ".gaslighting/GASLIGHTING.md",
18
+ ".gaslighting/PRD.md",
19
+ ".gaslighting/MISSING_INFO.md",
20
+ ".gaslighting/ASSUMPTIONS.md",
21
+ ".gaslighting/DECISION_LOG.md",
22
+ ".gaslighting/MEMORY.md",
23
+ ".gaslighting/AGENT_RUNTIME.md",
24
+ ".gaslighting/PROJECT_CARE.md",
25
+ ];
26
+ export async function runCockpit(rawUserRequest = "", options) {
27
+ await openCockpit(rawUserRequest, { ...options, generate: Boolean(rawUserRequest) && options.generate !== false, force: options.force ?? true });
28
+ }
29
+ export async function runStart(rawUserRequest, options) {
30
+ await openCockpit(rawUserRequest, { ...options, generate: true, force: options.force ?? true });
31
+ }
32
+ export function runCodexRun(rawUserRequest, options) {
33
+ const root = resolve(options.path ?? process.cwd());
34
+ const input = makeInput(rawUserRequest, options);
35
+ writeDocs(root, generateDocs(input, root).docs, options.force ?? true, false);
36
+ writeDocs(root, generateCodexInstallDocs(), true, false);
37
+ const result = launchCodex({ root, request: rawUserRequest, codexArgs: options.codexArgs });
38
+ printBanner("CODEX RUN");
39
+ console.log(result.message);
40
+ console.log(result.command);
41
+ }
42
+ async function openCockpit(rawUserRequest, options) {
43
+ const root = resolve(options.path ?? process.cwd());
44
+ let currentRequest = rawUserRequest || readExistingRequest(root) || "Build a practical MVP without shrinking scope.";
45
+ let currentInput = makeInput(currentRequest, options);
46
+ if (options.generate) {
47
+ writeDocs(root, generateDocs(currentInput, root).docs, options.force ?? true, false);
48
+ writeDocs(root, generateCodexInstallDocs(), true, false);
49
+ }
50
+ const requestedPort = Number.parseInt(options.port ?? "0", 10);
51
+ const server = createServer(async (request, response) => {
52
+ try {
53
+ if (request.method === "GET" && request.url === "/")
54
+ return sendHtml(response, renderCockpitHtml());
55
+ if (request.method === "GET" && request.url === "/api/state")
56
+ return sendJson(response, buildState(root, currentRequest, currentInput, options));
57
+ if (request.method === "POST" && request.url === "/api/generate") {
58
+ const body = await readJson(request);
59
+ currentRequest = String(body.request || currentRequest);
60
+ currentInput = makeInput(currentRequest, options);
61
+ writeDocs(root, generateDocs(currentInput, root).docs, true, false);
62
+ return sendJson(response, { state: buildState(root, currentRequest, currentInput, options) });
63
+ }
64
+ if (request.method === "POST" && request.url === "/api/start-codex") {
65
+ const body = await readJson(request);
66
+ currentRequest = String(body.request || currentRequest);
67
+ currentInput = makeInput(currentRequest, options);
68
+ writeDocs(root, generateDocs(currentInput, root).docs, true, false);
69
+ const result = launchCodex({ root, request: currentRequest, codexArgs: options.codexArgs });
70
+ return sendJson(response, { ...result });
71
+ }
72
+ if (request.method === "POST" && request.url === "/api/connect-github") {
73
+ const body = await readJson(request);
74
+ const githubUrl = String(body.githubUrl || "");
75
+ const results = connectGitHubRemote(root, githubUrl, Boolean(body.forceRemote));
76
+ writeDocs(root, generateProjectCareDoc(currentInput, root), true, false);
77
+ return sendJson(response, { results, state: buildState(root, currentRequest, currentInput, options) });
78
+ }
79
+ sendJson(response, { message: "Not found." }, 404);
80
+ }
81
+ catch (error) {
82
+ const message = error instanceof Error ? error.message : "Unknown cockpit error.";
83
+ sendJson(response, { message }, 500);
84
+ }
85
+ });
86
+ await new Promise((resolveListen) => server.listen(requestedPort, "127.0.0.1", resolveListen));
87
+ const address = server.address();
88
+ const port = typeof address === "object" && address ? address.port : requestedPort;
89
+ const url = `http://127.0.0.1:${port}`;
90
+ printBanner("MISSION CONTROL");
91
+ console.log(`Project: ${currentRequest}`);
92
+ console.log(`Root: ${root}`);
93
+ console.log(`URL: ${url}`);
94
+ console.log("Close this terminal session to stop the cockpit.");
95
+ if (options.open !== false)
96
+ openBrowser(url);
97
+ }
98
+ function buildState(root, request, input, options) {
99
+ const analysis = analyze(input);
100
+ const docsPresent = Object.fromEntries(docFiles.map((file) => [file, existsSync(join(root, file))]));
101
+ return {
102
+ version: packageVersion,
103
+ root,
104
+ request,
105
+ analysis,
106
+ purpose: projectPurpose(analysis.projectType),
107
+ pages: pageList(analysis.projectType),
108
+ care: inspectProjectCare(root),
109
+ docsPresent,
110
+ codexArgs: options.codexArgs || getDefaultCodexArgs(),
111
+ codexAvailable: isCommandAvailable("codex"),
112
+ };
113
+ }
114
+ function makeInput(rawUserRequest, options) {
115
+ return {
116
+ rawUserRequest,
117
+ projectType: options.type,
118
+ language: options.lang ?? "en",
119
+ mode: "hardcore",
120
+ fullScope: true,
121
+ noTodo: true,
122
+ noShortcut: true,
123
+ force: options.force ?? true,
124
+ };
125
+ }
126
+ function readExistingRequest(root) {
127
+ const carePath = join(root, ".gaslighting", "PROJECT_CARE.md");
128
+ if (!existsSync(carePath))
129
+ return undefined;
130
+ const content = readFileSync(carePath, "utf8");
131
+ return /^- Original request: (.+)$/m.exec(content)?.[1];
132
+ }
133
+ function isCommandAvailable(command) {
134
+ try {
135
+ if (platform() === "win32")
136
+ execFileSync("where", [command], { stdio: "ignore" });
137
+ else
138
+ execFileSync("sh", ["-lc", `command -v ${command}`], { stdio: "ignore" });
139
+ return true;
140
+ }
141
+ catch {
142
+ return false;
143
+ }
144
+ }
145
+ function openBrowser(url) {
146
+ const command = platform() === "win32"
147
+ ? { file: "cmd.exe", args: ["/c", "start", "", url] }
148
+ : platform() === "darwin"
149
+ ? { file: "open", args: [url] }
150
+ : { file: "xdg-open", args: [url] };
151
+ try {
152
+ const child = spawn(command.file, command.args, { detached: true, stdio: "ignore" });
153
+ child.unref();
154
+ }
155
+ catch {
156
+ // The printed URL is the fallback.
157
+ }
158
+ }
159
+ async function readJson(request) {
160
+ const chunks = [];
161
+ for await (const chunk of request)
162
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
163
+ if (chunks.length === 0)
164
+ return {};
165
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
166
+ }
167
+ function sendHtml(response, html) {
168
+ response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
169
+ response.end(html);
170
+ }
171
+ function sendJson(response, data, status = 200) {
172
+ response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
173
+ response.end(JSON.stringify(data));
174
+ }
@@ -18,6 +18,7 @@ export function runDoctor(options) {
18
18
  fileCheck(root, ".gaslighting/MISSING_INFO.md"),
19
19
  fileCheck(root, ".gaslighting/DECISION_LOG.md"),
20
20
  fileCheck(root, ".gaslighting/MEMORY.md"),
21
+ fileCheck(root, ".gaslighting/AGENT_RUNTIME.md"),
21
22
  fileCheck(root, ".gaslighting/PROJECT_CARE.md"),
22
23
  { name: ".gaslighting/GASLIGHTING.md contains project purpose", ok: /Project Purpose/i.test(gaslighting) },
23
24
  { name: ".gaslighting/GASLIGHTING.md contains no-fake-completion rules", ok: /No Fake Completion|Fake completion/i.test(gaslighting) },