git-intent
Advanced tools
Comparing version
#!/usr/bin/env node | ||
import{Command as xt}from"commander";import H from"node:path";import v from"execa";import I from"fs-extra";import{nanoid as q}from"nanoid";function _(i){return q(i)}import U from"node:path";import V from"fs-extra";function h(){let i=U.join("package.json");return V.readJSONSync(i)}import Y from"simple-git";var x=i=>Y(i);async function k(i){let t=x(i);try{await t.checkIsRepo()}catch{throw new Error("Not a git repository")}}async function M(i){return(await x(i).revparse(["--abbrev-ref","HEAD"])).trim()}async function X(i,t){return(await x(t).commit(i)).commit}var C=x();var D=class i{static instance;REFS_PREFIX="refs/intentional-commits";storageFilename;GIT_DIR=".git";gitRoot;constructor(){this.storageFilename=process.env.VITEST?"test_intents.json":"intents.json"}static getInstance(){return i.instance||(i.instance=new i),i.instance}setGitRoot(t){this.gitRoot=t}async getGitRoot(){return this.gitRoot?this.gitRoot:process.cwd()}async getCommitsDir(){let t=await this.getGitRoot();return H.join(t,this.GIT_DIR,"intentional-commits")}async getCommitsFile(){let t=await this.getCommitsDir();return H.join(t,"commits.json")}getInitialData(){return{version:h().version,commits:[]}}async ensureCommitsDir(){let t=await this.getCommitsDir(),e=await this.getCommitsFile();await I.ensureDir(t);try{await I.access(e)}catch{await I.writeJSON(e,this.getInitialData())}}migrateData(t){return t}async loadCommits(){let t=await this.getGitRoot();await k(t),await this.ensureCommitsDir();try{let e=await C.cwd(t).show(`${this.REFS_PREFIX}/commits:${this.storageFilename}`);return this.migrateData(JSON.parse(e)).commits}catch{let e=await this.getCommitsFile();return this.migrateData(await I.readJSON(e)).commits}}async saveCommitsData(t){let e=await this.getGitRoot(),o=await this.getCommitsFile(),s=JSON.stringify(t,null,2),{stdout:r}=await v("git",["hash-object","-w","--stdin"],{input:s,cwd:e}),c=`100644 blob ${r.trim()} ${this.storageFilename} | ||
`,{stdout:m}=await v("git",["mktree"],{input:c,cwd:e}),{stdout:p}=await v("git",["commit-tree",m.trim(),"-m","Update intent commits"],{cwd:e});await C.cwd(e).raw(["update-ref",`${this.REFS_PREFIX}/commits`,p.trim()]),await I.writeJSON(o,t,{spaces:2})}async saveCommits(t){let e={version:h().version,commits:t};await this.saveCommitsData(e)}async addCommit(t){let e=await this.loadCommits(),o=_(8),s={version:h().version,commits:[...e,{...t,id:o}]};return await this.saveCommitsData(s),o}async updateCommitMessage(t,e){let o=await this.loadCommits(),s=o.find(c=>c.id===t);if(!s)throw new Error("Commit not found");let r={version:h().version,commits:o.map(c=>c.id===t?{...s,message:e}:c)};await this.saveCommitsData(r)}async deleteCommit(t){let o=(await this.loadCommits()).filter(s=>s.id!==t);await this.saveCommits(o)}async clearCommits(){let t=await this.getGitRoot(),e=await this.getCommitsFile();await I.remove(e),await C.cwd(t).raw(["update-ref","-d",`${this.REFS_PREFIX}/commits`])}async initializeRefs(){let t=await this.getGitRoot();await k(t);try{await C.cwd(t).raw(["show-ref","--verify",`${this.REFS_PREFIX}/commits`])}catch{let e=this.getInitialData(),o=JSON.stringify(e,null,2),{stdout:s}=await v("git",["hash-object","-w","--stdin"],{input:o,cwd:t}),r=`100644 blob ${s.trim()} ${this.storageFilename} | ||
`,{stdout:c}=await v("git",["mktree"],{input:r,cwd:t}),{stdout:m}=await v("git",["commit-tree",c.trim(),"-m","Initialize intent commits"],{cwd:t});await C.cwd(t).raw(["update-ref",`${this.REFS_PREFIX}/commits`,m.trim()])}}},a=D.getInstance();import L from"chalk";import{Command as K}from"commander";var Q=new K().command("list").description("List all intentional commits").action(async()=>{let i=await a.loadCommits();if(i.length===0){console.log("No intents found");return}console.log(` | ||
Created:`);let t=i.filter(o=>o.status==="created");for(let o of t)console.log(L.white(` [${o.id}] ${o.message}`));console.log(` | ||
In Progress:`);let e=i.filter(o=>o.status==="in_progress");for(let o of e)console.log(L.blue(` [${o.id}] ${o.message}`))}),S=Q;import z from"chalk";import{Command as Z}from"commander";import tt from"prompts";var et=new Z().command("start").argument("[id]","Intent ID").description("Start working on a planned intent").action(async i=>{let t=await a.loadCommits(),e=i;if(!e){let r=t.filter(m=>m.status==="created");if(r.length===0){console.log("No created intents found");return}e=(await tt({type:"select",name:"id",message:"Select an intent to start:",choices:r.map(m=>({title:`${m.message} (${m.id})`,value:m.id}))})).id}if(!e){console.error("No intent selected");return}let o=t.find(r=>r.id===e);if(!o){console.error("Intent not found");return}if(o.status!=="created"){console.error("Intent is not in created status");return}let s=await M();o.status="in_progress",o.metadata.startedAt=new Date().toISOString(),o.metadata.branch=s,await a.saveCommits(t),console.log(z.green("\u2713 Started working on:")),console.log(`ID: ${z.blue(o.id)}`),console.log(`Message: ${o.message}`)}),$=et;import b from"chalk";import{Command as ot}from"commander";var it=new ot().command("show").description("Show current intention").action(async()=>{let t=(await a.loadCommits()).find(e=>e.status==="in_progress");if(!t){console.log("No active intention");return}console.log(b.blue("Current intention:")),console.log(`ID: ${b.dim(t.id)}`),console.log(`Message: ${t.message}`),console.log(`Status: ${b.yellow(t.status)}`),console.log(`Created at: ${new Date(t.metadata.createdAt).toLocaleString()}`)}),R=it;import st from"chalk";import{Command as nt}from"commander";var at=new nt().command("commit").description("Complete current intention and commit").option("-m, --message <message>","Additional commit message").action(async i=>{let e=(await a.loadCommits()).find(s=>s.status==="in_progress");if(!e){console.log("No active intention");return}let o=i.message?`${e.message} | ||
${i.message}`:e.message;await X(o),await a.deleteCommit(e.id),console.log(st.green("\u2713 Intention completed and committed"))}),P=at;import F from"chalk";import{Command as rt}from"commander";import mt from"prompts";var ct=new rt().command("drop").description("Drop a planned intent").argument("[id]","Intent ID").option("-a, --all","Drop all created intents").action(async(i,t)=>{let e=await a.loadCommits(),o=e.filter(m=>m.status==="created");if(t.all){await a.saveCommits([]),console.log(F.green("\u2713 All created intents removed"));return}let s=i;if(!s){if(o.length===0){console.log("No created intents found. Nothing to remove.");return}s=(await mt({type:"select",name:"id",message:"Select an intent to remove:",choices:o.map(p=>({title:`${p.message} (${p.id})`,value:p.id}))})).id}if(!s){console.error("No intent selected");return}let r=e.find(m=>m.id===s);if(!r){console.error("Intent not found");return}if(r.status!=="created"){console.error("Can only remove intents in created status");return}let c=e.filter(m=>m.id!==s);await a.saveCommits(c),console.log(F.green("\u2713 Intent removed:")),console.log(`ID: ${F.blue(r.id)}`),console.log(`Message: ${r.message}`)}),T=ct;import N from"chalk";import{Command as dt}from"commander";import lt from"prompts";var gt=new dt().command("cancel").description("Cancel current intention").action(async()=>{let i=await a.loadCommits(),t=i.find(s=>s.status==="in_progress");if(!t){console.log("No active intention");return}let{action:e}=await lt({type:"select",name:"action",message:"What would you like to do with the intent?",choices:[{title:"Reset to created status",value:"reset"},{title:"Delete the intent",value:"delete"}]});if(!e)return;let o;e==="reset"?(o=i.map(s=>s.id===t.id?{...s,status:"created",metadata:{...s.metadata,startedAt:void 0}}:s),console.log(N.green("\u2713 Intent reset to created status:"))):(o=i.filter(s=>s.id!==t.id),console.log(N.green("\u2713 Intent deleted:"))),await a.saveCommits(o),console.log(`ID: ${N.blue(t.id)}`),console.log(`Message: ${t.message}`),console.log(` | ||
Note: Your staged changes are preserved.`)}),O=gt;import{Command as pt}from"commander";import ft from"prompts";var ut=new pt().command("reset").description("Reset all intents").action(async()=>{(await ft({type:"confirm",name:"reset",message:"Are you sure you want to reset all intents?",initial:!1})).reset&&(await a.clearCommits(),console.log("All intents reset"))}),E=ut;import g from"chalk";import{Command as wt}from"commander";import B from"external-editor";import y from"prompts";var ht=new wt().command("divide").description("Divide an intent into smaller parts").action(async()=>{let i=await a.loadCommits();if(i.length===0){console.log("No intents found to divide");return}let e=(await y({type:"select",name:"id",message:"Select an intent to divide:",choices:i.map(n=>({title:`[${n.status==="created"?"Created":"In Progress"}] ${n.message.split(` | ||
`)[0]} (${n.id})`,value:n.id})),onState:n=>{n.aborted&&process.nextTick(()=>{process.exit(0)})}})).id;if(!e){console.log("No intent selected");return}let o=i.find(n=>n.id===e);if(!o){console.error("Intent not found");return}console.log(g.blue(` | ||
Original commit:`),o.message);let s=[];console.log(g.yellow(` | ||
Dividing commit into two tasks.`)),console.log(g.dim("Tip: Enter a title directly for a simple task, or leave it empty to open an editor for a detailed commit message.")),console.log(g.blue(` | ||
First task:`));let{taskTitle:r}=await y({type:"text",name:"taskTitle",message:"Task 1 title:",onState:n=>{n.aborted&&process.nextTick(()=>{process.exit(0)})}});if(r&&r.trim()!=="")s.push(r.trim());else{console.log(g.dim("Opening editor for commit message. Save and close the editor when done."));let n=o.message;n=`# Enter commit message for the first task | ||
// src/index.ts | ||
import { program } from "commander"; | ||
// src/utils/storage.ts | ||
import path2 from "node:path"; | ||
import execa from "execa"; | ||
import fs2 from "fs-extra"; | ||
// src/utils/generateId.ts | ||
import { nanoid } from "nanoid"; | ||
function generateId(size) { | ||
return nanoid(size); | ||
} | ||
// src/utils/get-package-info.ts | ||
import path from "node:path"; | ||
import { fileURLToPath } from "node:url"; | ||
import fs from "fs-extra"; | ||
function getPackageInfo() { | ||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = path.dirname(__filename); | ||
const packageJsonPath = path.join(__dirname, "..", "package.json"); | ||
return fs.readJSONSync(packageJsonPath); | ||
} | ||
// src/utils/git.ts | ||
import simpleGit from "simple-git"; | ||
var createGit = (cwd) => simpleGit(cwd); | ||
async function checkIsRepo(cwd) { | ||
const git = createGit(cwd); | ||
try { | ||
await git.checkIsRepo(); | ||
} catch { | ||
throw new Error("Not a git repository"); | ||
} | ||
} | ||
async function getCurrentBranch(cwd) { | ||
const git = createGit(cwd); | ||
const branch = await git.revparse(["--abbrev-ref", "HEAD"]); | ||
return branch.trim(); | ||
} | ||
async function createCommit(message, cwd) { | ||
const git = createGit(cwd); | ||
const result = await git.commit(message); | ||
return result.commit; | ||
} | ||
var git_default = createGit(); | ||
// src/utils/storage.ts | ||
var GitIntentionalCommitStorage = class _GitIntentionalCommitStorage { | ||
static instance; | ||
REFS_PREFIX = "refs/intentional-commits"; | ||
storageFilename; | ||
GIT_DIR = ".git"; | ||
gitRoot; | ||
constructor() { | ||
this.storageFilename = process.env.VITEST ? "test_intents.json" : "intents.json"; | ||
} | ||
static getInstance() { | ||
if (!_GitIntentionalCommitStorage.instance) { | ||
_GitIntentionalCommitStorage.instance = new _GitIntentionalCommitStorage(); | ||
} | ||
return _GitIntentionalCommitStorage.instance; | ||
} | ||
setGitRoot(root) { | ||
this.gitRoot = root; | ||
} | ||
async getGitRoot() { | ||
if (this.gitRoot) return this.gitRoot; | ||
return process.cwd(); | ||
} | ||
async getCommitsDir() { | ||
const root = await this.getGitRoot(); | ||
return path2.join(root, this.GIT_DIR, "intentional-commits"); | ||
} | ||
async getCommitsFile() { | ||
const commitsDir = await this.getCommitsDir(); | ||
return path2.join(commitsDir, "commits.json"); | ||
} | ||
getInitialData() { | ||
return { | ||
version: getPackageInfo().version, | ||
commits: [] | ||
}; | ||
} | ||
async ensureCommitsDir() { | ||
const commitsDir = await this.getCommitsDir(); | ||
const commitsFile = await this.getCommitsFile(); | ||
await fs2.ensureDir(commitsDir); | ||
try { | ||
await fs2.access(commitsFile); | ||
} catch { | ||
await fs2.writeJSON(commitsFile, this.getInitialData()); | ||
} | ||
} | ||
// NOTE: Migrate old data | ||
migrateData(data) { | ||
return data; | ||
} | ||
async loadCommits() { | ||
const root = await this.getGitRoot(); | ||
await checkIsRepo(root); | ||
await this.ensureCommitsDir(); | ||
try { | ||
const result = await git_default.cwd(root).show(`${this.REFS_PREFIX}/commits:${this.storageFilename}`); | ||
const data = this.migrateData(JSON.parse(result)); | ||
console.log(data); | ||
return data.commits; | ||
} catch { | ||
const commitsFile = await this.getCommitsFile(); | ||
const data = this.migrateData(await fs2.readJSON(commitsFile)); | ||
console.log(data); | ||
return data.commits; | ||
} | ||
} | ||
async saveCommitsData(data) { | ||
const root = await this.getGitRoot(); | ||
const commitsFile = await this.getCommitsFile(); | ||
const content = JSON.stringify(data, null, 2); | ||
const { stdout: hash } = await execa("git", ["hash-object", "-w", "--stdin"], { input: content, cwd: root }); | ||
const treeContent = `100644 blob ${hash.trim()} ${this.storageFilename} | ||
`; | ||
const { stdout: treeHash } = await execa("git", ["mktree"], { input: treeContent, cwd: root }); | ||
const { stdout: commitHash } = await execa("git", ["commit-tree", treeHash.trim(), "-m", "Update intent commits"], { | ||
cwd: root | ||
}); | ||
await git_default.cwd(root).raw(["update-ref", `${this.REFS_PREFIX}/commits`, commitHash.trim()]); | ||
await fs2.writeJSON(commitsFile, data, { spaces: 2 }); | ||
} | ||
async saveCommits(commits) { | ||
const data = { | ||
version: getPackageInfo().version, | ||
commits | ||
}; | ||
await this.saveCommitsData(data); | ||
} | ||
async addCommit(commit2) { | ||
const currentCommits = await this.loadCommits(); | ||
const newCommitId = generateId(8); | ||
const data = { | ||
version: getPackageInfo().version, | ||
commits: [...currentCommits, { ...commit2, id: newCommitId }] | ||
}; | ||
await this.saveCommitsData(data); | ||
return newCommitId; | ||
} | ||
async updateCommitMessage(id, message) { | ||
const currentCommits = await this.loadCommits(); | ||
const existingCommit = currentCommits.find((c) => c.id === id); | ||
if (!existingCommit) { | ||
throw new Error("Commit not found"); | ||
} | ||
const data = { | ||
version: getPackageInfo().version, | ||
commits: currentCommits.map((c) => c.id === id ? { ...existingCommit, message } : c) | ||
}; | ||
await this.saveCommitsData(data); | ||
} | ||
async deleteCommit(id) { | ||
const currentCommits = await this.loadCommits(); | ||
const newCommits = currentCommits.filter((c) => c.id !== id); | ||
await this.saveCommits(newCommits); | ||
} | ||
async clearCommits() { | ||
const root = await this.getGitRoot(); | ||
const commitsFile = await this.getCommitsFile(); | ||
await fs2.remove(commitsFile); | ||
await git_default.cwd(root).raw(["update-ref", "-d", `${this.REFS_PREFIX}/commits`]); | ||
} | ||
async initializeRefs() { | ||
const root = await this.getGitRoot(); | ||
await checkIsRepo(root); | ||
try { | ||
await git_default.cwd(root).raw(["show-ref", "--verify", `${this.REFS_PREFIX}/commits`]); | ||
} catch { | ||
const initialData = this.getInitialData(); | ||
const content = JSON.stringify(initialData, null, 2); | ||
const { stdout: hash } = await execa("git", ["hash-object", "-w", "--stdin"], { input: content, cwd: root }); | ||
const treeContent = `100644 blob ${hash.trim()} ${this.storageFilename} | ||
`; | ||
const { stdout: treeHash } = await execa("git", ["mktree"], { input: treeContent, cwd: root }); | ||
const { stdout: commitHash } = await execa( | ||
"git", | ||
["commit-tree", treeHash.trim(), "-m", "Initialize intent commits"], | ||
{ cwd: root } | ||
); | ||
await git_default.cwd(root).raw(["update-ref", `${this.REFS_PREFIX}/commits`, commitHash.trim()]); | ||
} | ||
} | ||
}; | ||
var storage = GitIntentionalCommitStorage.getInstance(); | ||
// src/commands/list.ts | ||
import chalk from "chalk"; | ||
import { Command } from "commander"; | ||
var list = new Command().command("list").description("List all intentional commits").action(async () => { | ||
const commits = await storage.loadCommits(); | ||
if (commits.length === 0) { | ||
console.log("No intents found"); | ||
return; | ||
} | ||
console.log("\nCreated:"); | ||
const createdCommits = commits.filter((commit2) => commit2.status === "created"); | ||
for (const commit2 of createdCommits) { | ||
console.log(chalk.white(` [${commit2.id}] ${commit2.message}`)); | ||
} | ||
console.log("\nIn Progress:"); | ||
const inProgressCommits = commits.filter((commit2) => commit2.status === "in_progress"); | ||
for (const commit2 of inProgressCommits) { | ||
console.log(chalk.blue(` [${commit2.id}] ${commit2.message}`)); | ||
} | ||
}); | ||
var list_default = list; | ||
// src/commands/start.ts | ||
import chalk2 from "chalk"; | ||
import { Command as Command2 } from "commander"; | ||
import prompts from "prompts"; | ||
var start = new Command2().command("start").argument("[id]", "Intent ID").description("Start working on a planned intent").action(async (id) => { | ||
const commits = await storage.loadCommits(); | ||
let selectedId = id; | ||
if (!selectedId) { | ||
const createdCommits = commits.filter((c) => c.status === "created"); | ||
if (createdCommits.length === 0) { | ||
console.log("No created intents found"); | ||
return; | ||
} | ||
const response = await prompts({ | ||
type: "select", | ||
name: "id", | ||
message: "Select an intent to start:", | ||
choices: createdCommits.map((c) => ({ | ||
title: `${c.message} (${c.id})`, | ||
value: c.id | ||
})) | ||
}); | ||
selectedId = response.id; | ||
} | ||
if (!selectedId) { | ||
console.error("No intent selected"); | ||
return; | ||
} | ||
const targetCommit = commits.find((c) => c.id === selectedId); | ||
if (!targetCommit) { | ||
console.error("Intent not found"); | ||
return; | ||
} | ||
if (targetCommit.status !== "created") { | ||
console.error("Intent is not in created status"); | ||
return; | ||
} | ||
const currentBranch = await getCurrentBranch(); | ||
targetCommit.status = "in_progress"; | ||
targetCommit.metadata.startedAt = (/* @__PURE__ */ new Date()).toISOString(); | ||
targetCommit.metadata.branch = currentBranch; | ||
await storage.saveCommits(commits); | ||
console.log(chalk2.green("\u2713 Started working on:")); | ||
console.log(`ID: ${chalk2.blue(targetCommit.id)}`); | ||
console.log(`Message: ${targetCommit.message}`); | ||
}); | ||
var start_default = start; | ||
// src/commands/show.ts | ||
import chalk3 from "chalk"; | ||
import { Command as Command3 } from "commander"; | ||
var show = new Command3().command("show").description("Show current intention").action(async () => { | ||
const commits = await storage.loadCommits(); | ||
const currentCommit = commits.find((c) => c.status === "in_progress"); | ||
if (!currentCommit) { | ||
console.log("No active intention"); | ||
return; | ||
} | ||
console.log(chalk3.blue("Current intention:")); | ||
console.log(`ID: ${chalk3.dim(currentCommit.id)}`); | ||
console.log(`Message: ${currentCommit.message}`); | ||
console.log(`Status: ${chalk3.yellow(currentCommit.status)}`); | ||
console.log(`Created at: ${new Date(currentCommit.metadata.createdAt).toLocaleString()}`); | ||
}); | ||
var show_default = show; | ||
// src/commands/commit.ts | ||
import chalk4 from "chalk"; | ||
import { Command as Command4 } from "commander"; | ||
var commit = new Command4().command("commit").description("Complete current intention and commit").option("-m, --message <message>", "Additional commit message").action(async (options) => { | ||
const commits = await storage.loadCommits(); | ||
const currentCommit = commits.find((c) => c.status === "in_progress"); | ||
if (!currentCommit) { | ||
console.log("No active intention"); | ||
return; | ||
} | ||
const message = options.message ? `${currentCommit.message} | ||
${options.message}` : currentCommit.message; | ||
await createCommit(message); | ||
await storage.deleteCommit(currentCommit.id); | ||
console.log(chalk4.green("\u2713 Intention completed and committed")); | ||
}); | ||
var commit_default = commit; | ||
// src/commands/drop.ts | ||
import chalk5 from "chalk"; | ||
import { Command as Command5 } from "commander"; | ||
import prompts2 from "prompts"; | ||
var drop = new Command5().command("drop").description("Drop a planned intent").argument("[id]", "Intent ID").option("-a, --all", "Drop all created intents").action(async (id, options) => { | ||
const commits = await storage.loadCommits(); | ||
const createdCommits = commits.filter((c) => c.status === "created"); | ||
if (options.all) { | ||
await storage.saveCommits([]); | ||
console.log(chalk5.green("\u2713 All created intents removed")); | ||
return; | ||
} | ||
let selectedId = id; | ||
if (!selectedId) { | ||
if (createdCommits.length === 0) { | ||
console.log("No created intents found. Nothing to remove."); | ||
return; | ||
} | ||
const response = await prompts2({ | ||
type: "select", | ||
name: "id", | ||
message: "Select an intent to remove:", | ||
choices: createdCommits.map((c) => ({ | ||
title: `${c.message} (${c.id})`, | ||
value: c.id | ||
})) | ||
}); | ||
selectedId = response.id; | ||
} | ||
if (!selectedId) { | ||
console.error("No intent selected"); | ||
return; | ||
} | ||
const targetCommit = commits.find((c) => c.id === selectedId); | ||
if (!targetCommit) { | ||
console.error("Intent not found"); | ||
return; | ||
} | ||
if (targetCommit.status !== "created") { | ||
console.error("Can only remove intents in created status"); | ||
return; | ||
} | ||
const updatedCommits = commits.filter((c) => c.id !== selectedId); | ||
await storage.saveCommits(updatedCommits); | ||
console.log(chalk5.green("\u2713 Intent removed:")); | ||
console.log(`ID: ${chalk5.blue(targetCommit.id)}`); | ||
console.log(`Message: ${targetCommit.message}`); | ||
}); | ||
var drop_default = drop; | ||
// src/commands/cancel.ts | ||
import chalk6 from "chalk"; | ||
import { Command as Command6 } from "commander"; | ||
import prompts3 from "prompts"; | ||
var cancel = new Command6().command("cancel").description("Cancel current intention").action(async () => { | ||
const commits = await storage.loadCommits(); | ||
const currentCommit = commits.find((c) => c.status === "in_progress"); | ||
if (!currentCommit) { | ||
console.log("No active intention"); | ||
return; | ||
} | ||
const { action } = await prompts3({ | ||
type: "select", | ||
name: "action", | ||
message: "What would you like to do with the intent?", | ||
choices: [ | ||
{ title: "Reset to created status", value: "reset" }, | ||
{ title: "Delete the intent", value: "delete" } | ||
] | ||
}); | ||
if (!action) { | ||
return; | ||
} | ||
let updatedCommits; | ||
if (action === "reset") { | ||
updatedCommits = commits.map( | ||
(c) => c.id === currentCommit.id ? { ...c, status: "created", metadata: { ...c.metadata, startedAt: void 0 } } : c | ||
); | ||
console.log(chalk6.green("\u2713 Intent reset to created status:")); | ||
} else { | ||
updatedCommits = commits.filter((c) => c.id !== currentCommit.id); | ||
console.log(chalk6.green("\u2713 Intent deleted:")); | ||
} | ||
await storage.saveCommits(updatedCommits); | ||
console.log(`ID: ${chalk6.blue(currentCommit.id)}`); | ||
console.log(`Message: ${currentCommit.message}`); | ||
console.log("\nNote: Your staged changes are preserved."); | ||
}); | ||
var cancel_default = cancel; | ||
// src/commands/reset.ts | ||
import { Command as Command7 } from "commander"; | ||
import prompts4 from "prompts"; | ||
var reset = new Command7().command("reset").description("Reset all intents").action(async () => { | ||
const response = await prompts4({ | ||
type: "confirm", | ||
name: "reset", | ||
message: "Are you sure you want to reset all intents?", | ||
initial: false | ||
}); | ||
if (!response.reset) { | ||
return; | ||
} | ||
await storage.clearCommits(); | ||
console.log("All intents reset"); | ||
}); | ||
var reset_default = reset; | ||
// src/commands/divide.ts | ||
import chalk7 from "chalk"; | ||
import { Command as Command8 } from "commander"; | ||
import edit from "external-editor"; | ||
import prompts5 from "prompts"; | ||
var divide = new Command8().command("divide").description("Divide an intent into smaller parts").action(async () => { | ||
const commits = await storage.loadCommits(); | ||
if (commits.length === 0) { | ||
console.log("No intents found to divide"); | ||
return; | ||
} | ||
const response = await prompts5({ | ||
type: "select", | ||
name: "id", | ||
message: "Select an intent to divide:", | ||
choices: commits.map((c) => ({ | ||
title: `[${c.status === "created" ? "Created" : "In Progress"}] ${c.message.split("\n")[0]} (${c.id})`, | ||
value: c.id | ||
})), | ||
onState: (state) => { | ||
if (state.aborted) { | ||
process.nextTick(() => { | ||
process.exit(0); | ||
}); | ||
} | ||
} | ||
}); | ||
const selectedId = response.id; | ||
if (!selectedId) { | ||
console.log("No intent selected"); | ||
return; | ||
} | ||
const targetCommit = commits.find((c) => c.id === selectedId); | ||
if (!targetCommit) { | ||
console.error("Intent not found"); | ||
return; | ||
} | ||
console.log(chalk7.blue("\nOriginal commit:"), targetCommit.message); | ||
const tasks = []; | ||
console.log(chalk7.yellow("\nDividing commit into two tasks.")); | ||
console.log( | ||
chalk7.dim( | ||
"Tip: Enter a title directly for a simple task, or leave it empty to open an editor for a detailed commit message." | ||
) | ||
); | ||
console.log(chalk7.blue("\nFirst task:")); | ||
const { taskTitle: firstTaskTitle } = await prompts5({ | ||
type: "text", | ||
name: "taskTitle", | ||
message: "Task 1 title:", | ||
onState: (state) => { | ||
if (state.aborted) { | ||
process.nextTick(() => { | ||
process.exit(0); | ||
}); | ||
} | ||
} | ||
}); | ||
if (firstTaskTitle && firstTaskTitle.trim() !== "") { | ||
tasks.push(firstTaskTitle.trim()); | ||
} else { | ||
console.log(chalk7.dim("Opening editor for commit message. Save and close the editor when done.")); | ||
let initialText = targetCommit.message; | ||
initialText = `# Enter commit message for the first task | ||
# Lines starting with # will be ignored | ||
${n}`;let u=B.edit(n,{postfix:".git-intent-divide"}).split(` | ||
`).filter(w=>!w.trim().startsWith("#")).join(` | ||
`).trim();if(!u){console.log("No message provided for the first task. Operation cancelled.");return}s.push(u)}console.log(g.blue(` | ||
Second task:`));let{taskTitle:c}=await y({type:"text",name:"taskTitle",message:"Task 2 title:",onState:n=>{n.aborted&&process.nextTick(()=>{process.exit(0)})}});if(c&&c.trim()!=="")s.push(c.trim());else{console.log(g.dim("Opening editor for commit message. Save and close the editor when done."));let u=B.edit(`# Enter commit message for the second task | ||
# Lines starting with # will be ignored | ||
`,{postfix:".git-intent-divide"}).split(` | ||
`).filter(w=>!w.trim().startsWith("#")).join(` | ||
`).trim();if(!u){console.log("No message provided for the second task. Operation cancelled.");return}s.push(u)}console.log(g.blue(` | ||
Tasks to create:`)),s.forEach((n,f)=>{let u=n.split(` | ||
`)[0];if(console.log(`${f+1}. ${u}`),n.includes(` | ||
`)){let w=n.split(` | ||
`).slice(1).join(" ").trim(),J=w.length>60?`${w.substring(0,57)}...`:w;J&&console.log(` ${g.dim(J)}`)}});let{confirmed:m}=await y({type:"confirm",name:"confirmed",message:"Do you want to divide the commit with these tasks?",initial:!0,onState:n=>{n.aborted&&process.nextTick(()=>{process.exit(0)})}});if(!m){console.log("Operation cancelled.");return}let{shouldRemoveOriginal:p}=await y({type:"confirm",name:"shouldRemoveOriginal",message:"Do you want to remove the original commit?",initial:!1,onState:n=>{n.aborted&&process.nextTick(()=>{process.exit(0)})}}),G=[];for(let n of s){let f=await a.addCommit({message:n,status:"created",metadata:{createdAt:new Date().toISOString()}});G.push(f)}p&&await a.deleteCommit(o.id),console.log(g.green("\u2713 Successfully divided the commit:"));for(let n=0;n<s.length;n++){let f=s[n].split(` | ||
`)[0];console.log(`${n+1}. ${g.blue(f)} (ID: ${G[n]})`)}if(!p){console.log(g.yellow(` | ||
Original commit was kept:`));let n=o.message.split(` | ||
`)[0];console.log(`${g.blue(n)} (ID: ${o.id})`)}}),A=ht;import W from"chalk";import{Command as Ct}from"commander";import vt from"external-editor";var It=new Ct().command("add").description("Add a new intentional commit before starting work").argument("[message]","Intent message").action(async i=>{let t=i;t||(t=vt.edit("",{postfix:".git-intent"}).trim()),t||(console.error("Commit message is required"),process.exit(1));let e=await a.addCommit({message:t,status:"created",metadata:{createdAt:new Date().toISOString()}});console.log(W.green("\u2713 Intent created:")),console.log(`ID: ${W.blue(e)}`),console.log(`Message: ${t}`)}),j=It;(async()=>{let i=new xt;await a.initializeRefs();let t=h();i.name("git-intent").description(t.description).version(t.version),i.addCommand(j).addCommand(S).addCommand($).addCommand(R).addCommand(P).addCommand(O).addCommand(E).addCommand(A).addCommand(T),i.parse()})(); | ||
${initialText}`; | ||
const message = edit.edit(initialText, { | ||
postfix: ".git-intent-divide" | ||
}); | ||
const fullMessage = message.split("\n").filter((line) => !line.trim().startsWith("#")).join("\n").trim(); | ||
if (!fullMessage) { | ||
console.log("No message provided for the first task. Operation cancelled."); | ||
return; | ||
} | ||
tasks.push(fullMessage); | ||
} | ||
console.log(chalk7.blue("\nSecond task:")); | ||
const { taskTitle: secondTaskTitle } = await prompts5({ | ||
type: "text", | ||
name: "taskTitle", | ||
message: "Task 2 title:", | ||
onState: (state) => { | ||
if (state.aborted) { | ||
process.nextTick(() => { | ||
process.exit(0); | ||
}); | ||
} | ||
} | ||
}); | ||
if (secondTaskTitle && secondTaskTitle.trim() !== "") { | ||
tasks.push(secondTaskTitle.trim()); | ||
} else { | ||
console.log(chalk7.dim("Opening editor for commit message. Save and close the editor when done.")); | ||
const initialText = "# Enter commit message for the second task\n# Lines starting with # will be ignored\n"; | ||
const message = edit.edit(initialText, { | ||
postfix: ".git-intent-divide" | ||
}); | ||
const fullMessage = message.split("\n").filter((line) => !line.trim().startsWith("#")).join("\n").trim(); | ||
if (!fullMessage) { | ||
console.log("No message provided for the second task. Operation cancelled."); | ||
return; | ||
} | ||
tasks.push(fullMessage); | ||
} | ||
console.log(chalk7.blue("\nTasks to create:")); | ||
tasks.forEach((task, index) => { | ||
const title = task.split("\n")[0]; | ||
console.log(`${index + 1}. ${title}`); | ||
if (task.includes("\n")) { | ||
const preview = task.split("\n").slice(1).join(" ").trim(); | ||
const shortPreview = preview.length > 60 ? `${preview.substring(0, 57)}...` : preview; | ||
if (shortPreview) { | ||
console.log(` ${chalk7.dim(shortPreview)}`); | ||
} | ||
} | ||
}); | ||
const { confirmed } = await prompts5({ | ||
type: "confirm", | ||
name: "confirmed", | ||
message: "Do you want to divide the commit with these tasks?", | ||
initial: true, | ||
onState: (state) => { | ||
if (state.aborted) { | ||
process.nextTick(() => { | ||
process.exit(0); | ||
}); | ||
} | ||
} | ||
}); | ||
if (!confirmed) { | ||
console.log("Operation cancelled."); | ||
return; | ||
} | ||
const { shouldRemoveOriginal } = await prompts5({ | ||
type: "confirm", | ||
name: "shouldRemoveOriginal", | ||
message: "Do you want to remove the original commit?", | ||
initial: false, | ||
onState: (state) => { | ||
if (state.aborted) { | ||
process.nextTick(() => { | ||
process.exit(0); | ||
}); | ||
} | ||
} | ||
}); | ||
const newCommitIds = []; | ||
for (const task of tasks) { | ||
const newCommitId = await storage.addCommit({ | ||
message: task, | ||
status: "created", | ||
metadata: { | ||
createdAt: (/* @__PURE__ */ new Date()).toISOString() | ||
} | ||
}); | ||
newCommitIds.push(newCommitId); | ||
} | ||
if (shouldRemoveOriginal) { | ||
await storage.deleteCommit(targetCommit.id); | ||
} | ||
console.log(chalk7.green("\u2713 Successfully divided the commit:")); | ||
for (let i = 0; i < tasks.length; i++) { | ||
const title = tasks[i].split("\n")[0]; | ||
console.log(`${i + 1}. ${chalk7.blue(title)} (ID: ${newCommitIds[i]})`); | ||
} | ||
if (!shouldRemoveOriginal) { | ||
console.log(chalk7.yellow("\nOriginal commit was kept:")); | ||
const originalTitle = targetCommit.message.split("\n")[0]; | ||
console.log(`${chalk7.blue(originalTitle)} (ID: ${targetCommit.id})`); | ||
} | ||
}); | ||
var divide_default = divide; | ||
// src/commands/add.ts | ||
import chalk8 from "chalk"; | ||
import { Command as Command9 } from "commander"; | ||
import edit2 from "external-editor"; | ||
var add = new Command9().command("add").description("Add a new intentional commit before starting work").argument("[message]", "Intent message").action(async (message) => { | ||
let commitMessage = message; | ||
if (!commitMessage) { | ||
const text = edit2.edit("", { | ||
postfix: ".git-intent" | ||
}); | ||
commitMessage = text.trim(); | ||
} | ||
if (!commitMessage) { | ||
console.error("Commit message is required"); | ||
process.exit(1); | ||
} | ||
const newCommitId = await storage.addCommit({ | ||
message: commitMessage, | ||
status: "created", | ||
metadata: { | ||
createdAt: (/* @__PURE__ */ new Date()).toISOString() | ||
} | ||
}); | ||
console.log(chalk8.green("\u2713 Intent created:")); | ||
console.log(`ID: ${chalk8.blue(newCommitId)}`); | ||
console.log(`Message: ${commitMessage}`); | ||
}); | ||
var add_default = add; | ||
// src/index.ts | ||
(async () => { | ||
await storage.initializeRefs(); | ||
program.addCommand(add_default).addCommand(list_default).addCommand(start_default).addCommand(show_default).addCommand(commit_default).addCommand(cancel_default).addCommand(reset_default).addCommand(divide_default).addCommand(drop_default); | ||
program.parse(); | ||
})(); |
{ | ||
"name": "git-intent", | ||
"version": "0.0.7", | ||
"version": "0.0.8", | ||
"description": "Git workflow tool for intentional commits — define your commit intentions first for clearer, more atomic changes.", | ||
@@ -35,3 +35,3 @@ "keywords": [ | ||
"chalk": "^4.1.2", | ||
"commander": "^11.1.0", | ||
"commander": "^13.1.0", | ||
"execa": "^5.1.1", | ||
@@ -46,3 +46,3 @@ "external-editor": "^3.1.0", | ||
"devDependencies": { | ||
"@biomejs/biome": "1.5.3", | ||
"@biomejs/biome": "^1.9.4", | ||
"@types/fs-extra": "^11.0.4", | ||
@@ -53,2 +53,3 @@ "@types/node": "^20.11.24", | ||
"tsup": "^8.0.2", | ||
"type-fest": "^4.39.1", | ||
"typescript": "^5.3.3", | ||
@@ -55,0 +56,0 @@ "vitest": "^1.3.1" |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
26412
45.86%600
823.08%2
-33.33%9
12.5%1
Infinity%+ Added
- Removed
Updated