{"_id":"@amarnotcool/repotool","_rev":"5-c7f5a6cf7ef764af96331194a2537f65","name":"@amarnotcool/repotool","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@amarnotcool/repotool","version":"0.1.0","keywords":["git","cli","graph","diff","myers","zero-dependency","no-dependencies","stdlib"],"license":"MIT","_id":"@amarnotcool/repotool@0.1.0","maintainers":[{"name":"amarnotcool","email":"devprataprathore346@gmail.com"}],"homepage":"https://github.com/amarnotcool/repotool#readme","bugs":{"url":"https://github.com/amarnotcool/repotool/issues"},"bin":{"repotool":"bin/repotool.js"},"dist":{"shasum":"1b2b09f744a6c57d8b1526711352de7971b01af2","tarball":"https://registry.npmjs.org/@amarnotcool/repotool/-/repotool-0.1.0.tgz","fileCount":14,"integrity":"sha512-U4/j3OS5Duek6Oh+aEWuM5Kj9/oPTySrlZDz+A2dKdgKxsCnZV5cxd26kLLO8fBOX+XHV8b0RkY8cXvJrMc85w==","signatures":[{"sig":"MEYCIQCcBcJk+PP/3p7kgyrFaxgqNn+l+Ni21WRoC6sDh0dRBAIhAK3mg0ZifvS2hQ47Qzulm26TpRhTMY+uhugOCN3JK1FQ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":55824},"main":"src/git-reader.js","engines":{"node":">=18"},"gitHead":"db31ce072c96bd8628a0f913e83ca4c25df54f50","scripts":{"test":"node --test test/*.test.js","verify":"sh verify-zero-deps.sh"},"_npmUser":{"name":"amarnotcool","email":"devprataprathore346@gmail.com"},"repository":{"url":"git+https://github.com/amarnotcool/repotool.git","type":"git"},"_npmVersion":"10.9.3","description":"Zero-dependency CLI that reads, graphs, queries and diffs a git repository using only the Node.js standard library.","directories":{},"_nodeVersion":"22.20.0","dependencies":{},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/repotool_0.1.0_1787256686319_0.30587715258876935","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."},"0.2.0":{"name":"@amarnotcool/repotool","version":"0.2.0","keywords":["git","cli","graph","diff","myers","zero-dependency","no-dependencies","stdlib"],"license":"MIT","_id":"@amarnotcool/repotool@0.2.0","maintainers":[{"name":"amarnotcool","email":"devprataprathore346@gmail.com"}],"homepage":"https://github.com/amarnotcool/repotool#readme","bugs":{"url":"https://github.com/amarnotcool/repotool/issues"},"bin":{"repotool":"bin/repotool.js"},"dist":{"shasum":"4de18e6524892fe0556fa24cf5499880b9daf04c","tarball":"https://registry.npmjs.org/@amarnotcool/repotool/-/repotool-0.2.0.tgz","fileCount":20,"integrity":"sha512-9RMDj/Fo8u3qbWhN/CzqNrAbaPp7xyLRhBtVVGG+1F/5zDZuTw74fc7LYqxiTSusFTV0rRelPgy7LTGxkZLe4g==","signatures":[{"sig":"MEQCIDk54oKQYEFzlf5iqWKKkomgeuE+uREMm+0vChoio2b3AiA4lYOtAIfkk9G5M/FdA+PVEk14S/LGe2KIulnoBxP2eg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":125170},"main":"src/index.js","engines":{"node":">=18"},"gitHead":"4b829a51797e9ea884dd5e2d6587a52d2d5955bb","scripts":{"test":"node --test test/*.test.js","verify":"sh verify-zero-deps.sh"},"_npmUser":{"name":"amarnotcool","email":"devprataprathore346@gmail.com"},"repository":{"url":"git+https://github.com/amarnotcool/repotool.git","type":"git"},"_npmVersion":"10.9.3","description":"Zero-dependency CLI that reads, graphs, queries and diffs a git repository using only the Node.js standard library.","directories":{},"_nodeVersion":"22.20.0","dependencies":{},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/repotool_0.2.0_1787403956734_0.7634391062941572","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."},"0.3.0":{"name":"@amarnotcool/repotool","version":"0.3.0","keywords":["git","cli","graph","diff","myers","zero-dependency","no-dependencies","stdlib"],"license":"MIT","_id":"@amarnotcool/repotool@0.3.0","maintainers":[{"name":"amarnotcool","email":"devprataprathore346@gmail.com"}],"homepage":"https://github.com/amarnotcool/repotool#readme","bugs":{"url":"https://github.com/amarnotcool/repotool/issues"},"bin":{"repotool":"bin/repotool.js"},"dist":{"shasum":"189912b7596719ec3fb5aa01cccf324b6d4857bf","tarball":"https://registry.npmjs.org/@amarnotcool/repotool/-/repotool-0.3.0.tgz","fileCount":23,"integrity":"sha512-QghnizlpY7WFICtN/xpDE9N32jsGeM1JbVguWicr3BOaaiHBQez20uLtRiOUlN4bwGKgOuTgopQqpn17vcrxvA==","signatures":[{"sig":"MEUCIQCrrwE0n74SOGF9FF9r1kVvcsDqkcH9iw2+2QzAqmhQWwIgM4J/KhH42Xal9P9Pckxjem14SPpPScS76b7ZQkJQosY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":166256},"main":"src/index.js","engines":{"node":">=18"},"gitHead":"2e54d77e46b1e6220c580ad4756275748d5c5557","scripts":{"test":"node --test test/*.test.js","verify":"sh verify-zero-deps.sh"},"_npmUser":{"name":"amarnotcool","email":"devprataprathore346@gmail.com"},"repository":{"url":"git+https://github.com/amarnotcool/repotool.git","type":"git"},"_npmVersion":"10.9.3","description":"Zero-dependency CLI that reads, graphs, queries and diffs a git repository using only the Node.js standard library.","directories":{},"_nodeVersion":"22.20.0","dependencies":{},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/repotool_0.3.0_1787614635777_0.28771765751863954","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."},"0.4.0":{"name":"@amarnotcool/repotool","version":"0.4.0","keywords":["git","cli","graph","diff","myers","zero-dependency","no-dependencies","stdlib"],"license":"MIT","_id":"@amarnotcool/repotool@0.4.0","maintainers":[{"name":"amarnotcool","email":"devprataprathore346@gmail.com"}],"homepage":"https://github.com/amarnotcool/repotool#readme","bugs":{"url":"https://github.com/amarnotcool/repotool/issues"},"bin":{"repotool":"bin/repotool.js"},"dist":{"shasum":"c58e8baec8ce9033f48939a9298d7b506a6836c9","tarball":"https://registry.npmjs.org/@amarnotcool/repotool/-/repotool-0.4.0.tgz","fileCount":29,"integrity":"sha512-sAOjvyfmpYFG9ylJk82S9JSipQDpvojnVGPCZZo0X4wkZ2RveoinWHOLnQvq/UAq5e8BrUznF8eE5UXYB6y6/Q==","signatures":[{"sig":"MEQCIGrYu90A4nlvZq+4dHlDmkPAcjSN8Mmj5t0dmKQpco1KAiAMvMicZ17YW8RVglf2Z2ieVZJWbiAP+RtJHfV5oSm50Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":216403},"main":"src/index.js","engines":{"node":">=18"},"gitHead":"611cb01559f12da9d399672a75ac95ff7a2b740c","scripts":{"test":"node --test test/*.test.js","verify":"sh verify-zero-deps.sh"},"_npmUser":{"name":"amarnotcool","email":"devprataprathore346@gmail.com"},"repository":{"url":"git+https://github.com/amarnotcool/repotool.git","type":"git"},"_npmVersion":"10.9.3","description":"Zero-dependency CLI that reads, graphs, queries and diffs a git repository using only the Node.js standard library.","directories":{},"_nodeVersion":"22.20.0","dependencies":{},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/repotool_0.4.0_1787618647008_0.6234821926266123","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."}},"time":{"created":"2026-08-20T20:11:26.102Z","modified":"2026-08-31T08:41:09.360Z","0.1.0":"2026-08-20T20:11:26.481Z","0.2.0":"2026-08-22T13:05:56.864Z","0.3.0":"2026-08-24T23:37:15.914Z","0.4.0":"2026-08-25T00:44:07.148Z"},"bugs":{"url":"https://github.com/amarnotcool/repotool/issues"},"license":"MIT","homepage":"https://github.com/amarnotcool/repotool#readme","keywords":["git","cli","graph","diff","myers","zero-dependency","no-dependencies","stdlib"],"repository":{"url":"git+https://github.com/amarnotcool/repotool.git","type":"git"},"description":"Zero-dependency CLI that reads, graphs, queries and diffs a git repository using only the Node.js standard library.","maintainers":[{"name":"amarnotcool","email":"devprataprathore346@gmail.com"}],"readme":"# repotool\r\n\r\nA CLI that reads, graphs, questions and diffs a git repository — built on the\r\nNode.js standard library and nothing else. No `chalk`, no `commander`, no\r\n`diff`, no `simple-git`. `dependencies` in `package.json` is empty, and\r\n`verify-zero-deps.sh` proves it.\r\n\r\n## Run it\r\n\r\nNothing to install, nothing to build — run it straight from the registry\r\ninside any git repository:\r\n\r\n```sh\r\nnpx @amarnotcool/repotool graph\r\n```\r\n\r\nOr install it once and use the short command everywhere:\r\n\r\n```sh\r\nnpm i -g @amarnotcool/repotool\r\nrepotool graph\r\n```\r\n\r\nThat global install pulls exactly one package: this one. `dependencies` is\r\nempty, so there is no transitive tree behind it.\r\n\r\nFrom a clone, no install step at all:\r\n\r\n```sh\r\nnode bin/repotool.js graph\r\n```\r\n\r\nNode 18 or newer and a `git` binary on `PATH` are the only requirements.\r\n\r\n## Commands\r\n\r\n### `repotool graph`\r\n\r\nParses the full commit and merge history and draws it as a lane graph.\r\n`*` is a commit, `M` a merge, `o` a root commit.\r\n\r\n```\r\n8 commit(s), on branch main\r\n\r\nM      f27040a 2026-08-20 Ada Lovelace (main) Merge hotfix\r\n|\\\r\n| *    2c2040d 2026-08-20 Ada Lovelace (hotfix) Hotfix\r\n| |\r\nM |    0eb3d2e 2026-08-20 Ada Lovelace Merge feature into main\r\n|\\|\\\r\n* | |  5a22eb4 2026-08-20 Ada Lovelace Add readme\r\n| |/\r\n| *    ab19254 2026-08-20 Grace Hopper (feature) Extend feature\r\n| |\r\n| *    ef60ae3 2026-08-20 Grace Hopper Add feature file\r\n|/\r\n*      01f599d 2026-08-20 Ada Lovelace Tweak app\r\n|\r\no      231d066 2026-08-20 Ada Lovelace Initial commit\r\n```\r\n\r\nFlags: `--limit N`, `--branch REF`, `--no-dates`, `--format ascii|svg`,\r\n`--output PATH`, `--repo PATH`, `--no-color`.\r\n\r\n#### SVG export\r\n\r\n`--format svg` renders the *same* layout through a second renderer — commit,\r\nmerge and root nodes, lane colours, connectors and labels — as an SVG document\r\non stdout, or into a file with `--output`:\r\n\r\n```sh\r\nrepotool graph --format svg > history.svg\r\nrepotool graph --format svg --output history.svg     # status line on stderr\r\nrepotool graph --branch main --limit 50 --format svg --output main.svg\r\n```\r\n\r\nLane assignment happens once, in `src/graph/build-graph.js`; the ASCII and SVG\r\nrenderers only draw what it decided, so they cannot disagree about structure.\r\nA parent outside the loaded history (`--limit`, or a shallow clone) is drawn as\r\na dashed stub rather than dropped.\r\n\r\n### `repotool stats`\r\n\r\nOne screen describing the repository: size, people, branches, churn, and where\r\nthe changes land.\r\n\r\n```\r\nrepotool stats — Zero Dependency  on branch main\r\n\r\ncommits        15\r\ncontributors   2\r\nbranches       2 local, 1 remote\r\nfiles touched  37\r\nline churn     10,666  +9,796 / -870\r\nhistory        2026-08-20 → 2026-08-25  (4 days)\r\nlast commit    13 minutes ago\r\n\r\nTop 3 contributors\r\n──────────────────\r\n13  ████████████████  Devpratap\r\n 2  ██                Devpratap Singh\r\n\r\nTop 3 most-changed files\r\n────────────────────────\r\n8 commits  497 lines  README.md\r\n7 commits  863 lines  bin/repotool.js\r\n6 commits  874 lines  src/query/handlers.js\r\n```\r\n\r\n### `repotool hotspots`\r\n\r\nFiles ranked by how much attention they attract — change frequency, how many\r\ndifferent people touch them, and how many lines move. The score weights all\r\nthree and the weighting is printed with the table rather than hidden.\r\n\r\n```sh\r\nrepotool hotspots\r\nrepotool hotspots --limit 25 --sort churn\r\n```\r\n\r\nSort by `score` (default), `commits`, `churn` or `authors`.\r\n\r\n#### Where the churn numbers come from\r\n\r\nEvery line-count figure in `stats`, `hotspots` and `ask` comes from a single\r\n`git log --numstat` pass over the whole history. Git emits no numstat for\r\nmerge commits, so **merges are excluded from churn** — that avoids counting the\r\nsame lines twice on a merge-heavy history, but it does mean a repository that\r\nsquash-merges everything will report less churn than the branch history\r\nsuggests. Every file-level answer reads that same pass, so `hotspots` and\r\n`ask \"which file changed the most often\"` cannot disagree with each other.\r\n\r\n### `repotool health`\r\n\r\nFour measurements of the repository, each printed next to the formula that\r\nproduced it. Nothing here is a judgement or an estimate — every number is\r\narithmetic over the same history `stats` and `hotspots` read, and `--help`\r\nprints the formulas, the bands and the warning thresholds in full.\r\n\r\n```\r\nrepotool health — repotool\r\n\r\nActivity         50  ██████████           1.5× the baseline pace (1.5/day vs 1/day), capped at 3×\r\nConcentration    77  ███████████████      23% of 10,722 churned lines are in the 3 busiest files\r\nStability        65  █████████████        6 commits of 17 mention a fix, bug, revert or regression\r\nCollaboration    24  █████                Devpratap made 76% of 17 commits\r\n\r\nOverall   54  FAIR\r\nequal-weighted mean of the scores above · bands: 80+ EXCELLENT · 60+ GOOD · 40+ FAIR · 0+ NEEDS ATTENTION\r\n\r\nWarnings\r\n────────\r\n! README.md changed in 9 of 17 commits (threshold: more than 5)\r\n! Devpratap made 76% of all commits (threshold: above 70%)\r\n```\r\n\r\n| Dimension | Formula (higher is better) |\r\n|---|---|\r\n| Activity | `min(recent commits/day ÷ baseline commits/day, 3) ÷ 3 × 100` |\r\n| Concentration | `100 − (churn in the 3 busiest files ÷ total churn × 100)` |\r\n| Stability | `100 − (subjects matching the fix pattern ÷ commits × 100)` |\r\n| Collaboration | `100 − (top contributor's commits ÷ total commits × 100)` |\r\n\r\nThe fix pattern is `\\b(fix|fixes|fixed|fixing|bug|bugs|bugfix|hotfix|revert|reverts|reverted|regression)\\b`,\r\ncase-insensitive — word boundaries, so `prefix` is not a fix and `debugger` is\r\nnot a bug.\r\n\r\n**Activity refuses to answer rather than guess.** It reuses the comparison\r\nbehind `ask \"why is this repository changing so much\"`, including that\r\nanswer's rule: when either period spans under a day, or there are fewer than\r\nfour commits, no ratio is printed and the dimension is left out of the overall\r\nmean instead of being filled in with a plausible number.\r\n\r\nWarnings print only when they trigger: one file changed in more than\r\n`max(5, 25% of all commits)` commits, more than 50% of all churn in the three\r\nbusiest files, or one contributor above 70% of all commits.\r\n\r\n### `repotool timeline`\r\n\r\nCommit activity per day (or week), scaled against the busiest bucket on screen.\r\n\r\n```\r\nrepotool timeline --limit 6\r\n\r\nRepository Activity\r\n───────────────────\r\n\r\nAug 20  ██████\r\nAug 21  ████████████████████████████████\r\nAug 22  ███\r\nAug 23  ·\r\nAug 24  ·\r\nAug 25  █████████\r\n\r\nCommits: 17  (2026-08-20 → 2026-08-25)\r\nPeak: Aug 21  (11 commits)\r\n```\r\n\r\nFlags: `--limit N` (recent buckets, default 30), `--by day|week`,\r\n`--metric commits|lines|contributors`.\r\n\r\nDays with no commits are shown as `·` rather than skipped — a gap in the\r\nhistory is information, and dropping it would quietly compress the time axis.\r\nBuckets use each commit's local calendar day, so an evening commit stays on its\r\nown day instead of sliding into the next one.\r\n\r\n### `repotool compare <refA> <refB>`\r\n\r\nWhat each of two refs has that the other does not — branches, tags or commits.\r\n\r\n```sh\r\nrepotool compare main feature\r\nrepotool compare v1.0 v2.0\r\nrepotool compare HEAD~10 HEAD --json\r\n```\r\n\r\n```\r\nrepotool compare — main vs feature\r\n\r\n                 main   feature\r\ncommits ahead       1   2\r\nmerges              —   —\r\nfiles changed       1   1\r\nchurn               1   3 +3 / -0\r\ncontributors        1   1\r\n\r\nonly on main: Ada Lovelace (1)\r\nonly on feature: Grace Hopper (2)\r\n```\r\n\r\nEach side is git's own `A..B` range — commits reachable from one ref and not\r\nthe other — folded through the same model `stats` and `hotspots` are built on,\r\nso \"ahead by\" here counts what git counts. A ref compared with itself reports\r\nzero difference and exits 0; refs with no common ancestor are reported as\r\nunrelated rather than treated as an error.\r\n\r\n### `repotool ask \"<question>\"`\r\n\r\nDeterministic answers to fixed question shapes — a keyword matcher over parsed\r\ngit data, not a language model, so the same question always gives the same\r\nanswer and nothing leaves your machine.\r\n\r\n```sh\r\nrepotool ask \"who last touched src/git-reader.js\"\r\nrepotool ask \"who works most on src/git-reader.js\"\r\nrepotool ask \"what has changed recently\"\r\nrepotool ask \"why is this repository changing so much\"\r\nrepotool ask \"how many commits by Ada\"\r\nrepotool ask \"what files changed in HEAD~2\"\r\nrepotool ask \"show the last 10 commits\"\r\nrepotool ask \"which file changed the most often\"\r\nrepotool ask \"what branches exist\"\r\nrepotool ask \"when was 01f599d committed\"\r\n```\r\n\r\n`why is this repository changing so much` is arithmetic, not opinion: it\r\ncompares the recent commit rate against the earlier baseline, names the files\r\nrecent churn concentrates in, and says who is driving it — and it declines to\r\nquote a per-day rate when the history is too compressed in time to support one.\r\n\r\nAsk something unsupported and it prints the list of what it can answer.\r\n\r\n### `repotool diff <commitA> [commitB]`\r\n\r\nColourised unified diff between two revisions, computed by our own Myers\r\ndiff implementation.\r\n\r\n```sh\r\nrepotool diff HEAD~3 HEAD\r\nrepotool diff main feature --context 5\r\nrepotool diff HEAD~1 HEAD --stat\r\n```\r\n\r\nLines that were *edited* rather than replaced get a second, character-level\r\nMyers pass — the same algorithm, run again over the two lines' characters — and\r\nonly the span that actually changed is underlined inside the usual red/green\r\nline. A pair is only treated as an edit when it clears three guards: at least\r\n30% of characters in common, no more than three separate changed runs per\r\nside, and runs averaging at least two characters. Anything else is a rewrite\r\nand stays whole-line coloured, because character-level detail on unrelated\r\nlines is noise rather than information. With `--no-color` (or `NO_COLOR`) the\r\nextra pass is skipped entirely: there is nothing to show it with.\r\n\r\n### `repotool completion <bash|zsh>`\r\n\r\nPrints a completion script for the named shell. See\r\n[Shell completion](#shell-completion) for installation.\r\n\r\n## Scripting / JSON output\r\n\r\n`stats`, `hotspots`, `health`, `timeline`, `ask` and `compare` take `--json`. The JSON goes to stdout and\r\nnothing else does, errors still go to stderr, and exit codes are unchanged — so\r\n`repotool stats --json | …` is safe to pipe. `graph` and `diff` have no\r\n`--json`: their value is the picture, and `--format svg` is graph's structured\r\nexport.\r\n\r\n```sh\r\nrepotool stats --json\r\nrepotool hotspots --json --limit 25 --sort churn\r\nrepotool health --json\r\nrepotool timeline --json --by week\r\nrepotool compare main feature --json\r\nrepotool ask \"who last touched src/app.js\" --json\r\n```\r\n\r\nField names are stable; new fields may be added, existing ones are not renamed.\r\n\r\n`stats --json`:\r\n\r\n```\r\n{\r\n  \"repository\": { \"path\", \"name\", \"head\": { \"empty\", \"detached\", \"branch\", \"hash\" } },\r\n  \"empty\":        boolean,\r\n  \"commits\":      { \"total\", \"merges\", \"first\", \"last\", \"spanDays\" },\r\n  \"contributors\": [ { \"name\", \"email\", \"commits\", \"merges\", \"added\", \"removed\",\r\n                      \"firstDate\", \"lastDate\" } ],\r\n  \"branches\":     { \"local\": [name], \"remote\": [name] },\r\n  \"totals\":       { \"filesTouched\", \"added\", \"removed\", \"churn\" },\r\n  \"topFiles\":     [ file ]\r\n}\r\n```\r\n\r\n`hotspots --json`:\r\n\r\n```\r\n{\r\n  \"repository\": { … as above … },\r\n  \"empty\":      boolean,\r\n  \"sort\":       \"score\" | \"commits\" | \"churn\" | \"authors\",\r\n  \"weights\":    { \"commits\", \"churn\", \"authors\" },\r\n  \"totalFiles\": number,\r\n  \"files\":      [ { \"rank\", \"score\", …file } ]\r\n}\r\n```\r\n\r\nA `file` everywhere above is\r\n`{ path, commits, authors, added, removed, churn, binary, firstDate, lastDate }`.\r\n\r\n`health --json` carries the evidence behind each score, not just the score, so\r\na script can see what produced it:\r\n\r\n```\r\n{\r\n  \"repository\": { … },\r\n  \"empty\":         boolean,\r\n  \"overall\":       { \"score\", \"band\", \"dimensions\": [measured dimension names] },\r\n  \"activity\":      { \"score\", \"formula\", \"ratio\", \"recentPerDay\", \"baselinePerDay\",\r\n                     \"comparable\", \"reason\" },\r\n  \"concentration\": { \"score\", \"formula\", \"share\", \"topChurn\", \"totalChurn\", \"files\" },\r\n  \"stability\":     { \"score\", \"formula\", \"pattern\", \"fixCommits\", \"totalCommits\", \"share\" },\r\n  \"collaboration\": { \"score\", \"formula\", \"topContributor\", \"topCommits\",\r\n                     \"totalCommits\", \"contributors\", \"share\" },\r\n  \"warnings\":      [ { \"code\", \"message\", \"value\", \"threshold\" } ]\r\n}\r\n```\r\n\r\nA dimension that could not be measured has `\"score\": null` and a `\"reason\"`,\r\nand its name is absent from `overall.dimensions`.\r\n\r\n`timeline --json`:\r\n\r\n```\r\n{\r\n  \"repository\": { … },\r\n  \"empty\":        boolean,\r\n  \"by\":           \"day\" | \"week\",\r\n  \"metric\":       \"commits\" | \"lines\" | \"contributors\",\r\n  \"buckets\":      [ { \"date\", \"commits\", \"added\", \"removed\", \"contributors\" } ],\r\n  \"peak\":         { \"date\", \"commits\" } | null,\r\n  \"totalCommits\": number\r\n}\r\n```\r\n\r\n`compare --json` describes both directions with the same shape, so a script can\r\nread them symmetrically:\r\n\r\n```\r\n{\r\n  \"refA\", \"refB\", \"hashA\", \"hashB\",\r\n  \"identical\":  boolean,\r\n  \"mergeBase\":  hash | null,\r\n  \"a\":          side,          // what refA has that refB does not\r\n  \"b\":          side,          // and the reverse\r\n  \"sharedContributors\": [name],\r\n  \"sharedFiles\":        [path]\r\n}\r\n\r\nside = { ref, range, commits, merges, filesChanged, added, removed, churn,\r\n         first, last, contributors, onlyContributors, files }\r\n```\r\n\r\n`ask --json` wraps the answer in the question that produced it, so a script\r\ndoes not have to keep its own note of what it asked:\r\n\r\n```\r\n{\r\n  \"question\": \"who last touched src/app.js\",\r\n  \"intent\":   \"who-touched\",\r\n  \"argument\": \"src/app.js\",\r\n  \"answer\":   { … shape depends on the intent … }\r\n}\r\n```\r\n\r\nThe text and JSON answers are built side by side from the same values in\r\n`src/query/handlers.js`, so the two can never report different facts.\r\n\r\n## Shell completion\r\n\r\n```sh\r\n# bash — for the current shell\r\neval \"$(repotool completion bash)\"\r\n\r\n# bash — permanently\r\nrepotool completion bash > ~/.local/share/bash-completion/completions/repotool\r\n\r\n# zsh — into a directory on your fpath, then restart the shell\r\nrepotool completion zsh > \"${fpath[1]}/_repotool\"\r\n```\r\n\r\nBoth scripts complete commands, each command's flags, and the fixed values a\r\nflag accepts (`--sort score|commits|churn|authors`, `--format ascii|svg`);\r\n`--repo` completes directories and `--output` completes files. They are\r\ngenerated from one description of the CLI surface in `src/completion.js`, so\r\nthe two shells cannot drift apart from each other or from the tool.\r\n\r\n## Zero-dependency proof\r\n\r\n```sh\r\nnode verify-zero-deps.js     # runs anywhere Node runs\r\nsh verify-zero-deps.sh       # same checks, POSIX shell\r\n```\r\n\r\nBoth check the same three things: the `dependencies` block is empty, every\r\n`require` in `src/`, `bin/` and `test/` resolves to a Node built-in or a\r\nrelative path, and no unexpected `node_modules` tree is present. The Node\r\nversion needs no shell, so it works on Windows without Git Bash.\r\n\r\nSee [STDLIB.md](STDLIB.md) for the full package-by-package substitution log.\r\n\r\n## Tests\r\n\r\n```sh\r\nnpm test        # or: node --test \"test/*.test.js\"\r\n```\r\n\r\nThe suite builds real throwaway git repositories and covers empty repos, a\r\nsingle root commit, merge commits, detached HEAD, malformed git output, a\r\nrandomised round-trip property for the diff algorithm, SVG well-formedness\r\n(checked with a small XML parser written for the tests, since Node has none),\r\n`--json` shapes, generated completion scripts (`bash -n`), and the dependency\r\nproof itself — planting a third-party import to confirm the checker fails.\r\n\r\n## Design\r\n\r\nNine commands over one shared reading layer. Feature modules never reach into\r\neach other's internals.\r\n\r\n```\r\nbin/repotool.js       argv routing, per-command help\r\nsrc/index.js          public API for using repotool as a library\r\nsrc/git-reader.js     the only file that talks to git\r\nsrc/analysis/         shared repo model + stats, hotspots, health, timeline,\r\n                      compare, and the JSON renderers\r\nsrc/graph/            DAG layout + ASCII and SVG renderers\r\nsrc/query/            question -> intent -> answer (text and JSON together)\r\nsrc/diff/             Myers diff + unified-diff renderer\r\nsrc/completion.js     generated bash and zsh completion scripts\r\nsrc/format.js         column alignment, number grouping, relative dates\r\nsrc/ansi.js           escape codes, TTY/NO_COLOR aware\r\n```\r\n\r\nEvery output format is a renderer over a shared model, never a second\r\nderivation of the same facts: ASCII and SVG share one lane layout, the human\r\nand JSON reports share one repo model, and each `ask` answer produces its text\r\nand its JSON side by side.\r\n\r\n`src/analysis/repo-model.js` reads the history once — commits, contributors,\r\nbranches and per-file churn, the last of these from a single\r\n`git log --numstat` — and every report and aggregate question is folded from\r\nthat one pass rather than re-deriving its own.\r\n\r\nFeature modules are required lazily, both in the CLI and in `src/index.js`, so\r\na problem in one feature cannot stop the other two from running.\r\n\r\n## Use it as a library\r\n\r\n```js\r\nconst {\r\n  readCommits, buildGraph, renderAscii, renderSvg,\r\n  buildRepoModel, statsJson, hotspotsJson,\r\n  diffLines, parseQuestion, answerQuestionJson,\r\n} = require('@amarnotcool/repotool');\r\n\r\nconst { commits } = readCommits({ cwd: '/path/to/repo' });\r\nconsole.log(renderAscii(buildGraph(commits)));\r\nconsole.log(renderSvg(buildGraph(commits)));\r\nconsole.log(statsJson(buildRepoModel({ cwd: '/path/to/repo' })));\r\n```\r\n\r\n`git-reader` shells out to the `git` binary for raw data only — commit\r\nmetadata, file lists, blob contents. Every piece of interpretation (graph\r\nlayout, question answering, diffing) is our own code.\r\n\r\n## License\r\n\r\nMIT — see [LICENSE](LICENSE).\r\n","readmeFilename":"README.md"}