@ggdna/dna-gg 0.1.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 (32) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -0
  3. package/dna/_dna.json +10 -0
  4. package/dna/_generated.json +204 -0
  5. package/dna/_vars.json +9 -0
  6. package/dna/doc/guides/develop-guide.md +41 -0
  7. package/dna/doc/guides/develop-without-ai-guide.md +63 -0
  8. package/dna/doc/guides/for-ai/ai-dev-guide.md +97 -0
  9. package/dna/doc/guides/install-guide/install-gg-guide.md +75 -0
  10. package/dna/doc/guides/install-guide/install-gg-workspace-guide.md +28 -0
  11. package/dna/doc/guides/install-guide.overrides.md +9 -0
  12. package/dna/dot-claude/skills/cleanup/SKILL.md +49 -0
  13. package/dna/dot-claude/skills/commit/SKILL.md +37 -0
  14. package/dna/dot-claude/skills/publish/SKILL.md +39 -0
  15. package/dna/dot-claude/skills/push/SKILL.md +21 -0
  16. package/dna/dot-claude/skills/ticket/SKILL.md +47 -0
  17. package/dna/dot-github/workflows/quick_check.yaml +20 -0
  18. package/dna/scripts/delete-feature-branch.js +97 -0
  19. package/dna/scripts/functions/colors.js +27 -0
  20. package/dna/scripts/functions/current-branch.js +11 -0
  21. package/dna/scripts/functions/directories.js +22 -0
  22. package/dna/scripts/functions/get-repo-urls.js +43 -0
  23. package/dna/scripts/functions/get-version.js +14 -0
  24. package/dna/scripts/functions/is-clean-repo.js +51 -0
  25. package/dna/scripts/functions/is-main-up-to-date.js +19 -0
  26. package/dna/scripts/functions/pull-request-url.js +17 -0
  27. package/dna/scripts/functions/run-command.js +22 -0
  28. package/dna/scripts/functions/sync-folders.js +46 -0
  29. package/dna/scripts/rename-class.js +147 -0
  30. package/dna/scripts/setup-github-repo.js +221 -0
  31. package/dna/scripts/wait-for-pr.js +90 -0
  32. package/package.json +45 -0
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: commit
3
+ description: Commits the ticket's work in every repo with `gg do commit`. Use when the user says "/commit" or asks to commit the current changes.
4
+ ---
5
+
6
+ # Commit
7
+
8
+ `gg do commit` runs the checks of each repo and commits only when they are
9
+ green. The message also becomes the `CHANGELOG.md` entry.
10
+
11
+ ## 1. Propose a message
12
+
13
+ Look at `git status` and `git diff` in the repos of the ticket. Propose one
14
+ imperative one-liner that says what the step does, not which files changed.
15
+ Let the user confirm it.
16
+
17
+ ## 2. Commit
18
+
19
+ From the ticket folder:
20
+
21
+ ```bash
22
+ gg do commit -m"<message>"
23
+ ```
24
+
25
+ Add `-l` when the message must not land in `CHANGELOG.md`.
26
+
27
+ ## 3. On a failing check
28
+
29
+ `gg do commit` stops at the first red check. Fix the cause, never the check,
30
+ then run the command again. Files generated by the DNA are committed
31
+ separately as `#gg: generated DNA` — never edit an instance by hand, change
32
+ its source under `dna/` instead.
33
+
34
+ ## Important
35
+
36
+ - Never commit without the confirmed message.
37
+ - Never push here — that is `/push`.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: publish
3
+ description: Prepares the release of a reviewed ticket and hands `gg do publish` over to the user. Use when the user says "/publish" or asks to release the ticket.
4
+ ---
5
+
6
+ # Publish
7
+
8
+ `gg do publish` merges the pull requests, publishes to the registry and
9
+ creates and pushes the version tags. It is interactive — the user runs it.
10
+
11
+ ## 1. Check the preconditions
12
+
13
+ - Every repo of the ticket is committed and pushed.
14
+ - The pull requests created by `gg do review` are green.
15
+
16
+ ## 2. Configure the publish
17
+
18
+ ```bash
19
+ gg do configure-publish
20
+ ```
21
+
22
+ Check per repo that the version bump matches what the ticket changed and
23
+ that `CHANGELOG.md` reads well.
24
+
25
+ ## 3. Let the user publish
26
+
27
+ Ask the user to run this **manually** from the ticket folder:
28
+
29
+ ```bash
30
+ gg do publish
31
+ ```
32
+
33
+ It asks whether the ticket and the remote feature branches may be deleted.
34
+ If the user declines, run `/cleanup` afterwards.
35
+
36
+ ## Important
37
+
38
+ - Never run `gg do publish` yourself.
39
+ - Never write versions or tags by hand.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: push
3
+ description: Pushes the ticket's commits with `gg do push`. Use when the user says "/push" or asks to push the current ticket.
4
+ ---
5
+
6
+ # Push
7
+
8
+ From the ticket folder:
9
+
10
+ ```bash
11
+ gg do push
12
+ ```
13
+
14
+ `gg do push` pushes the repos of the ticket in dependency order and refuses
15
+ repos that still carry uncommitted work. If it reports such a repo, run
16
+ `/commit` first and push again.
17
+
18
+ ## Important
19
+
20
+ - Push only when the user asked for it.
21
+ - Do not create pull requests here — `gg do review` does that.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: ticket
3
+ description: Creates a gg ticket and adds the repos it needs. Use when the user says "/ticket", "new ticket", "fix bug X", or "implement feature Y".
4
+ ---
5
+
6
+ # Create a ticket
7
+
8
+ All `gg do` commands run inside the workspace. Ask for the workspace
9
+ directory if none is known.
10
+
11
+ ## 1. Ask for the ticket data
12
+
13
+ - Ticket ID, e.g. `DnaJiraPrefix-145`
14
+ - Title, one imperative line, e.g. `Fix issue abc`
15
+
16
+ ## 2. Create the ticket
17
+
18
+ ```bash
19
+ cd ~/dev/ # workspace
20
+ gg do create ticket DnaJiraPrefix-145 -m"Fix issue abc"
21
+ cd tickets/DnaJiraPrefix-145
22
+ ```
23
+
24
+ ## 3. Choose the repos
25
+
26
+ Read the `index.md` of the candidate repos in `.ocean`. Tell the user which
27
+ repos the ticket needs and what you roughly want to change in each one. If a
28
+ part belongs to a domain that has no repo yet, say so and ask whether to
29
+ create one.
30
+
31
+ After the confirmation:
32
+
33
+ ```bash
34
+ gg do add repo1 repo2
35
+ ```
36
+
37
+ ## 4. Open the workspace
38
+
39
+ ```bash
40
+ gg do code
41
+ ```
42
+
43
+ ## Important
44
+
45
+ - Never add repos without confirmation. Rather too few than too many — more
46
+ can be added later.
47
+ - Do not start the implementation unasked.
@@ -0,0 +1,20 @@
1
+ # Runs the gg-js quick check on every push and pull request against main.
2
+
3
+ name: Tests
4
+
5
+ on:
6
+ push:
7
+ branches: ['main']
8
+ pull_request:
9
+ branches: ['main']
10
+
11
+ jobs:
12
+ build:
13
+ name: dnaGitQuickCheckPipelineName
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v5
18
+
19
+ # Uses the Node.js preinstalled on the runner image.
20
+ - run: npx @tssuite/gg-js one did commit
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ import { gray, green, red, yellow } from './functions/colors.js';
10
+ import { runCommand } from './functions/run-command.js';
11
+
12
+ // Check for uncommitted changes
13
+ function hasUncommittedChanges() {
14
+ console.log(gray('Check for uncommitted changes'));
15
+ const status = runCommand('git status --porcelain');
16
+ return status.length > 0;
17
+ }
18
+
19
+ // Check for unpushed commits
20
+ function hasUnpushedCommits(branch) {
21
+ try {
22
+ console.log(gray('Check for unpushed commits'));
23
+ runCommand(`git rev-parse --abbrev-ref ${branch}@{u}`);
24
+ const ahead = runCommand(`git rev-list --count ${branch}@{u}..${branch}`);
25
+ return parseInt(ahead, 10) > 0;
26
+ } catch {
27
+ return true;
28
+ }
29
+ }
30
+
31
+ // Try a test merge into main and check if it introduces changes
32
+ function isBranchEffectivelyMerged(featureBranch) {
33
+ console.log(gray('Check if feature was fully merged'));
34
+
35
+ try {
36
+ runCommand(`git merge --no-commit --no-ff ${featureBranch}`);
37
+ const changed = hasUncommittedChanges();
38
+ runCommand('git merge --abort');
39
+ return !changed;
40
+ } catch {
41
+ try {
42
+ runCommand('git merge --abort');
43
+ } catch {}
44
+ return false;
45
+ }
46
+ }
47
+
48
+ try {
49
+ console.log(gray('Get current branch name'));
50
+ const currentBranch = runCommand('git rev-parse --abbrev-ref HEAD');
51
+
52
+ if (currentBranch === 'main') {
53
+ console.log(yellow('Please call this script from a feature branch.'));
54
+ process.exit(0);
55
+ }
56
+
57
+ if (hasUncommittedChanges()) {
58
+ console.error(red('❌ You have uncommitted changes.'));
59
+ console.error(
60
+ yellow('Please commit or stash your changes before continuing.'),
61
+ );
62
+ process.exit(1);
63
+ }
64
+
65
+ if (hasUnpushedCommits(currentBranch)) {
66
+ console.error(red('❌ You have unpushed commits.'));
67
+ console.error(yellow('Please push your branch before continuing.'));
68
+ process.exit(1);
69
+ }
70
+
71
+ console.log(gray(`Fetching and pulling 'main'...`));
72
+ runCommand('git fetch');
73
+ runCommand('git checkout main');
74
+ runCommand('git pull origin main');
75
+
76
+ const isMerged = isBranchEffectivelyMerged(currentBranch);
77
+
78
+ if (isMerged) {
79
+ runCommand(`git branch -d ${currentBranch}`);
80
+ console.log(green(`✅ Branch '${currentBranch}' has been deleted.`));
81
+ } else {
82
+ console.error(
83
+ red(`❌ Branch '${currentBranch}' is not merged into 'main'.`),
84
+ );
85
+ console.log(
86
+ yellow(`Please (create and) merge a pull request and try again.`),
87
+ );
88
+ runCommand(`git checkout ${currentBranch}`);
89
+ console.log(gray(`Switched back to '${currentBranch}'.`));
90
+ }
91
+ } catch (error) {
92
+ console.error(red(`Error: ${error.message}`));
93
+ try {
94
+ runCommand('git merge --abort');
95
+ } catch {}
96
+ process.exit(1);
97
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ export const red = (str) => `\x1b[31m${str}\x1b[0m`;
10
+ export const green = (str) => `\x1b[32m${str}\x1b[0m`;
11
+ export const yellow = (str) => `\x1b[33m${str}\x1b[0m`;
12
+ export const blue = (str) => `\x1b[34m${str}\x1b[0m`;
13
+ export const magenta = (str) => `\x1b[35m${str}\x1b[0m`;
14
+ export const cyan = (str) => `\x1b[36m${str}\x1b[0m`;
15
+ export const white = (str) => `\x1b[37m${str}\x1b[0m`;
16
+ export const black = (str) => `\x1b[30m${str}\x1b[0m`;
17
+ export const bgRed = (str) => `\x1b[41m${str}\x1b[0m`;
18
+ export const bgGreen = (str) => `\x1b[42m${str}\x1b[0m`;
19
+ export const bgYellow = (str) => `\x1b[43m${str}\x1b[0m`;
20
+ export const bgBlue = (str) => `\x1b[44m${str}\x1b[0m`;
21
+ export const bgMagenta = (str) => `\x1b[45m${str}\x1b[0m`;
22
+ export const bgCyan = (str) => `\x1b[46m${str}\x1b[0m`;
23
+ export const bgWhite = (str) => `\x1b[47m${str}\x1b[0m`;
24
+ export const bgBlack = (str) => `\x1b[40m${str}\x1b[0m`;
25
+ export const gray = (str) => `\x1b[90m${str}\x1b[0m`;
26
+ export const lightGray = (str) => `\x1b[37m${str}\x1b[0m`;
27
+ export const reset = (str) => `\x1b[0m${str}\x1b[0m`;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ export const currentBranch = () => {
10
+ return execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
11
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ // Get the name of this file
10
+ import { dirname, join } from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ export const workSpaceDir = join(__dirname, '..', '..');
17
+ export const distDir = join(workSpaceDir, 'dist');
18
+ export const srcDir = join(workSpaceDir, 'src');
19
+ export const testDir = join(workSpaceDir, 'test');
20
+ export const scriptsDir = join(workSpaceDir, 'scripts');
21
+ export const goldensDir = join(testDir, 'goldens');
22
+ export const nodeModulesDir = join(workSpaceDir, 'node_modules');
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ // Import Node.js built-in module for working with child processes
10
+ import { exec } from 'child_process';
11
+ import { promisify } from 'util';
12
+ import { green, yellow } from './colors.js';
13
+
14
+ // Convert exec to return a Promise for async/await usage
15
+ const execAsync = promisify(exec);
16
+
17
+ // GitHub organization or user
18
+ const org = 'rljson';
19
+
20
+ export async function getRepoUrls() {
21
+ try {
22
+ // Use the GitHub CLI to list repositories in JSON format
23
+ const { stdout } = await execAsync(
24
+ `gh repo list ${org} --limit 1000 --json url`,
25
+ );
26
+
27
+ // Parse the JSON output
28
+ const repos = JSON.parse(stdout);
29
+
30
+ // Map to name + URL and print to console
31
+ return repos.map((repo) => repo.url);
32
+ } catch (error) {
33
+ if (error.message.match('gh auth login').length == 1) {
34
+ throw Error(
35
+ [yellow('Not yet logged in. Please run:'), green('gh auth login')].join(
36
+ '\n',
37
+ ),
38
+ );
39
+ }
40
+
41
+ throw error;
42
+ }
43
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ import { readFile } from 'fs/promises';
10
+
11
+ export const getVersion = async () => {
12
+ const packageJson = JSON.parse(await readFile('package.json', 'utf8'));
13
+ return packageJson.version;
14
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ // checkRepo.js
10
+ import { execSync } from 'child_process';
11
+
12
+ export function isCleanRepo(path) {
13
+ const cwd = path ?? '.';
14
+
15
+ try {
16
+ // Aktuellen Branch ermitteln
17
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
18
+ cwd,
19
+ })
20
+ .toString()
21
+ .trim();
22
+ if (branch !== 'main') {
23
+ return false;
24
+ }
25
+
26
+ // Git-Fetch ausführen (still)
27
+ execSync('git fetch', { stdio: 'ignore', cwd });
28
+
29
+ // Prüfen, ob Arbeitsverzeichnis sauber ist
30
+ const status = execSync('git status --porcelain', { cwd })
31
+ .toString()
32
+ .trim();
33
+ if (status.length > 0) {
34
+ return false;
35
+ }
36
+
37
+ // Prüfen, ob main auf dem neuesten Stand mit origin/main ist
38
+ const revList = execSync(
39
+ 'git rev-list --left-right --count origin/main...main',
40
+ { cwd },
41
+ )
42
+ .toString()
43
+ .trim();
44
+
45
+ const [behind, ahead] = revList.split('\t').map(Number);
46
+ return behind === 0 && ahead === 0;
47
+ } catch (err) {
48
+ console.error('Git error:', err.message);
49
+ return false;
50
+ }
51
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ export const isMainUpToDate = () => {
10
+ try {
11
+ execSync('git fetch origin main');
12
+ const localHash = execSync('git rev-parse main').toString().trim();
13
+ const remoteHash = execSync('git rev-parse origin/main').toString().trim();
14
+ return localHash === remoteHash;
15
+ } catch (error) {
16
+ console.error(red('Error checking branch status:'), error);
17
+ return false;
18
+ }
19
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ export function pullRequestUrl() {
10
+ const json = execSync('gh pr view --json url', {
11
+ encoding: 'utf-8',
12
+ }).trim();
13
+
14
+ const parsed = JSON.parse(json);
15
+ const url = parsed.url;
16
+ return url;
17
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ import { execSync } from 'child_process';
10
+ import { gray } from './colors.js';
11
+
12
+ // Execute a shell command and return trimmed output
13
+ // Execute a shell command and return trimmed output
14
+ export function runCommand(command, silent = true, logCommand = true) {
15
+ if (logCommand) {
16
+ console.log(gray(`${command}`));
17
+ }
18
+ return execSync(command, {
19
+ encoding: 'utf-8',
20
+ stdio: silent ? ['pipe', 'pipe', 'pipe'] : undefined,
21
+ }).trim();
22
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) ggsuite
4
+ *
5
+ * Use of this source code is governed by terms that can be
6
+ * found in the LICENSE file in the root of this package.
7
+ */
8
+
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+
12
+ // Create a node.js script that recursively copies all files from one folder
13
+ // to another
14
+
15
+ export async function syncFolders(
16
+ source,
17
+ target,
18
+ options = { excludeHidden: true },
19
+ ) {
20
+ if (!fs.existsSync(source)) {
21
+ console.error(`Source folder "${source}" does not exist.`);
22
+ return;
23
+ }
24
+
25
+ if (!fs.existsSync(target)) {
26
+ await fs.promises.mkdir(target, { recursive: true });
27
+ }
28
+
29
+ const items = await fs.promises.readdir(source);
30
+
31
+ for (const item of items) {
32
+ if (options.excludeHidden && item.startsWith('.')) {
33
+ continue; // Skip hidden files and folders
34
+ }
35
+
36
+ const sourcePath = path.join(source, item);
37
+ const targetPath = path.join(target, item);
38
+
39
+ const stat = await fs.promises.lstat(sourcePath);
40
+ if (stat.isDirectory()) {
41
+ await syncFolders(sourcePath, targetPath, options.excludeHidden);
42
+ } else {
43
+ await fs.promises.copyFile(sourcePath, targetPath);
44
+ }
45
+ }
46
+ }