diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fdcf9c9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "sdk/IdeaSDK"] + path = sdk/IdeaSDK + url = https://gitea.anthonybouteiller.ovh/blomios/IdeaSDK.git diff --git a/sdk/IdeaSDK b/sdk/IdeaSDK new file mode 160000 index 0000000..c322055 --- /dev/null +++ b/sdk/IdeaSDK @@ -0,0 +1 @@ +Subproject commit c322055edb6812874fe11eea21620479a64e5850 diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/HEAD b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/HEAD deleted file mode 100644 index b870d82..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/main diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/config b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/config deleted file mode 100644 index e8f1393..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/config +++ /dev/null @@ -1,11 +0,0 @@ -[core] - repositoryformatversion = 0 - filemode = true - bare = false - logallrefupdates = true -[remote "origin"] - url = https://gitea.anthonybouteiller.ovh/blomios/IdeaSDK.git - fetch = +refs/heads/*:refs/remotes/origin/* -[branch "main"] - remote = origin - merge = refs/heads/main diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/description b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/description deleted file mode 100644 index 498b267..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/applypatch-msg.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/applypatch-msg.sample deleted file mode 100755 index a5d7b84..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/applypatch-msg.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message taken by -# applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. The hook is -# allowed to edit the commit message file. -# -# To enable this hook, rename this file to "applypatch-msg". - -. git-sh-setup -commitmsg="$(git rev-parse --git-path hooks/commit-msg)" -test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} -: diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/commit-msg.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/commit-msg.sample deleted file mode 100755 index f7458ef..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/commit-msg.sample +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/sh -# -# An example hook script to check the commit log message. -# Called by "git commit" with one argument, the name of the file -# that has the commit message. The hook should exit with non-zero -# status after issuing an appropriate message if it wants to stop the -# commit. The hook is allowed to edit the commit message file. -# -# To enable this hook, rename this file to "commit-msg". - -# Uncomment the below to add a Signed-off-by line to the message. -# Doing this in a hook is a bad idea in general, but the prepare-commit-msg -# hook is more suited to it. -# -# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" - -# This example catches duplicate Signed-off-by lines and messages that -# would confuse 'git am'. - -ret=0 - -test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { - echo >&2 Duplicate Signed-off-by lines. - ret=1 -} - -comment_re="$( - { - git config --get-regexp "^core\.comment(char|string)\$" || - echo '#' - } | sed -n -e ' - ${ - s/^[^ ]* // - s|[][*./\]|\\&|g - s/^auto$/[#;@!$%^&|:]/ - p - }' -)" -scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$" -comment_line="^${comment_re}.*" -blank_line='^[ ]*$' -# Disallow lines starting with "diff -" or "Index: " in the body of the -# message. Stop looking if we see a scissors line. -line="$(sed -n -e " - # Skip comments and blank lines at the start of the file. - /${scissors_line}/q - /${comment_line}/d - /${blank_line}/d - # The first paragraph will become the subject header so - # does not need to be checked. - : subject - n - /${scissors_line}/q - /${blank_line}/!b subject - # Check the body of the message for problematic - # prefixes. - : body - n - /${scissors_line}/q - /${comment_line}/b body - /^diff -/{p;q;} - /^Index: /{p;q;} - b body - " "$1")" -if test -n "$line" -then - echo >&2 "Message contains a diff that will confuse 'git am'." - echo >&2 "To fix this indent the diff." - ret=1 -fi - -exit $ret diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/fsmonitor-watchman.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/fsmonitor-watchman.sample deleted file mode 100755 index 429e0a5..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/fsmonitor-watchman.sample +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use IPC::Open2; - -# An example hook script to integrate Watchman -# (https://facebook.github.io/watchman/) with git to speed up detecting -# new and modified files. -# -# The hook is passed a version (currently 2) and last update token -# formatted as a string and outputs to stdout a new update token and -# all files that have been modified since the update token. Paths must -# be relative to the root of the working tree and separated by a single NUL. -# -# To enable this hook, rename this file to "query-watchman" and set -# 'git config core.fsmonitor .git/hooks/query-watchman' -# -my ($version, $last_update_token) = @ARGV; - -# Uncomment for debugging -# print STDERR "$0 $version $last_update_token\n"; - -# Check the hook interface version -if ($version ne 2) { - die "Unsupported query-fsmonitor hook version '$version'.\n" . - "Falling back to scanning...\n"; -} - -my $git_work_tree = get_working_dir(); - -my $json_pkg; -eval { - require JSON::XS; - $json_pkg = "JSON::XS"; - 1; -} or do { - require JSON::PP; - $json_pkg = "JSON::PP"; -}; - -launch_watchman(); - -sub launch_watchman { - my $o = watchman_query(); - if (is_work_tree_watched($o)) { - output_result($o->{clock}, @{$o->{files}}); - } -} - -sub output_result { - my ($clockid, @files) = @_; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # binmode $fh, ":utf8"; - # print $fh "$clockid\n@files\n"; - # close $fh; - - binmode STDOUT, ":utf8"; - print $clockid; - print "\0"; - local $, = "\0"; - print @files; -} - -sub watchman_clock { - my $response = qx/watchman clock "$git_work_tree"/; - die "Failed to get clock id on '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - - return $json_pkg->new->utf8->decode($response); -} - -sub watchman_query { - my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') - or die "open2() failed: $!\n" . - "Falling back to scanning...\n"; - - # In the query expression below we're asking for names of files that - # changed since $last_update_token but not from the .git folder. - # - # To accomplish this, we're using the "since" generator to use the - # recency index to select candidate nodes and "fields" to limit the - # output to file names only. Then we're using the "expression" term to - # further constrain the results. - my $last_update_line = ""; - if (substr($last_update_token, 0, 1) eq "c") { - $last_update_token = "\"$last_update_token\""; - $last_update_line = qq[\n"since": $last_update_token,]; - } - my $query = <<" END"; - ["query", "$git_work_tree", {$last_update_line - "fields": ["name"], - "expression": ["not", ["dirname", ".git"]] - }] - END - - # Uncomment for debugging the watchman query - # open (my $fh, ">", ".git/watchman-query.json"); - # print $fh $query; - # close $fh; - - print CHLD_IN $query; - close CHLD_IN; - my $response = do {local $/; }; - - # Uncomment for debugging the watch response - # open ($fh, ">", ".git/watchman-response.json"); - # print $fh $response; - # close $fh; - - die "Watchman: command returned no output.\n" . - "Falling back to scanning...\n" if $response eq ""; - die "Watchman: command returned invalid output: $response\n" . - "Falling back to scanning...\n" unless $response =~ /^\{/; - - return $json_pkg->new->utf8->decode($response); -} - -sub is_work_tree_watched { - my ($output) = @_; - my $error = $output->{error}; - if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { - my $response = qx/watchman watch "$git_work_tree"/; - die "Failed to make watchman watch '$git_work_tree'.\n" . - "Falling back to scanning...\n" if $? != 0; - $output = $json_pkg->new->utf8->decode($response); - $error = $output->{error}; - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - # Uncomment for debugging watchman output - # open (my $fh, ">", ".git/watchman-output.out"); - # close $fh; - - # Watchman will always return all files on the first query so - # return the fast "everything is dirty" flag to git and do the - # Watchman query just to get it over with now so we won't pay - # the cost in git to look up each individual file. - my $o = watchman_clock(); - $error = $o->{error}; - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - output_result($o->{clock}, ("/")); - return 0; - } - - die "Watchman: $error.\n" . - "Falling back to scanning...\n" if $error; - - return 1; -} - -sub get_working_dir { - my $working_dir; - if ($^O =~ 'msys' || $^O =~ 'cygwin') { - $working_dir = Win32::GetCwd(); - $working_dir =~ tr/\\/\//; - } else { - require Cwd; - $working_dir = Cwd::cwd(); - } - - return $working_dir; -} diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/post-update.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/post-update.sample deleted file mode 100755 index ec17ec1..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/post-update.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare a packed repository for use over -# dumb transports. -# -# To enable this hook, rename this file to "post-update". - -exec git update-server-info diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-applypatch.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-applypatch.sample deleted file mode 100755 index 4142082..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-applypatch.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -. git-sh-setup -precommit="$(git rev-parse --git-path hooks/pre-commit)" -test -x "$precommit" && exec "$precommit" ${1+"$@"} -: diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-commit.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-commit.sample deleted file mode 100755 index 29ed5ee..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-commit.sample +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-commit". - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=$(git hash-object -t tree /dev/null) -fi - -# If you want to allow non-ASCII filenames set this variable to true. -allownonascii=$(git config --type=bool hooks.allownonascii) - -# Redirect output to stderr. -exec 1>&2 - -# Cross platform projects tend to avoid non-ASCII filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test $(git diff-index --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 -then - cat <<\EOF -Error: Attempt to add a non-ASCII file name. - -This can cause problems if you want to work with people on other platforms. - -To be portable it is advisable to rename the file. - -If you know what you are doing you can disable this check using: - - git config hooks.allownonascii true -EOF - exit 1 -fi - -# If there are whitespace errors, print the offending file names and fail. -exec git diff-index --check --cached $against -- diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-merge-commit.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-merge-commit.sample deleted file mode 100755 index 399eab1..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-merge-commit.sample +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git merge" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message to -# stderr if it wants to stop the merge commit. -# -# To enable this hook, rename this file to "pre-merge-commit". - -. git-sh-setup -test -x "$GIT_DIR/hooks/pre-commit" && - exec "$GIT_DIR/hooks/pre-commit" -: diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-push.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-push.sample deleted file mode 100755 index 4ce688d..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-push.sample +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -# An example hook script to verify what is about to be pushed. Called by "git -# push" after it has checked the remote status, but before anything has been -# pushed. If this script exits with a non-zero status nothing will be pushed. -# -# This hook is called with the following parameters: -# -# $1 -- Name of the remote to which the push is being done -# $2 -- URL to which the push is being done -# -# If pushing without using a named remote those arguments will be equal. -# -# Information about the commits which are being pushed is supplied as lines to -# the standard input in the form: -# -# -# -# This sample shows how to prevent push of commits where the log message starts -# with "WIP" (work in progress). - -remote="$1" -url="$2" - -zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" - exit 1 - fi - fi -done - -exit 0 diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-rebase.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-rebase.sample deleted file mode 100755 index 6cbef5c..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-rebase.sample +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006, 2008 Junio C Hamano -# -# The "pre-rebase" hook is run just before "git rebase" starts doing -# its job, and can prevent the command from running by exiting with -# non-zero status. -# -# The hook is called with the following parameters: -# -# $1 -- the upstream the series was forked from. -# $2 -- the branch being rebased (or empty when rebasing the current branch). -# -# This sample shows how to prevent topic branches that are already -# merged to 'next' branch from getting rebased, because allowing it -# would result in rebasing already published history. - -publish=next -basebranch="$1" -if test "$#" = 2 -then - topic="refs/heads/$2" -else - topic=`git symbolic-ref HEAD` || - exit 0 ;# we do not interrupt rebasing detached HEAD -fi - -case "$topic" in -refs/heads/??/*) - ;; -*) - exit 0 ;# we do not interrupt others. - ;; -esac - -# Now we are dealing with a topic branch being rebased -# on top of master. Is it OK to rebase it? - -# Does the topic really exist? -git show-ref -q "$topic" || { - echo >&2 "No such branch $topic" - exit 1 -} - -# Is topic fully merged to master? -not_in_master=`git rev-list --pretty=oneline ^master "$topic"` -if test -z "$not_in_master" -then - echo >&2 "$topic is fully merged to master; better remove it." - exit 1 ;# we could allow it, but there is no point. -fi - -# Is topic ever merged to next? If so you should not be rebasing it. -only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` -only_next_2=`git rev-list ^master ${publish} | sort` -if test "$only_next_1" = "$only_next_2" -then - not_in_topic=`git rev-list "^$topic" master` - if test -z "$not_in_topic" - then - echo >&2 "$topic is already up to date with master" - exit 1 ;# we could allow it, but there is no point. - else - exit 0 - fi -else - not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` - /usr/bin/perl -e ' - my $topic = $ARGV[0]; - my $msg = "* $topic has commits already merged to public branch:\n"; - my (%not_in_next) = map { - /^([0-9a-f]+) /; - ($1 => 1); - } split(/\n/, $ARGV[1]); - for my $elem (map { - /^([0-9a-f]+) (.*)$/; - [$1 => $2]; - } split(/\n/, $ARGV[2])) { - if (!exists $not_in_next{$elem->[0]}) { - if ($msg) { - print STDERR $msg; - undef $msg; - } - print STDERR " $elem->[1]\n"; - } - } - ' "$topic" "$not_in_next" "$not_in_master" - exit 1 -fi - -<<\DOC_END - -This sample hook safeguards topic branches that have been -published from being rewound. - -The workflow assumed here is: - - * Once a topic branch forks from "master", "master" is never - merged into it again (either directly or indirectly). - - * Once a topic branch is fully cooked and merged into "master", - it is deleted. If you need to build on top of it to correct - earlier mistakes, a new topic branch is created by forking at - the tip of the "master". This is not strictly necessary, but - it makes it easier to keep your history simple. - - * Whenever you need to test or publish your changes to topic - branches, merge them into "next" branch. - -The script, being an example, hardcodes the publish branch name -to be "next", but it is trivial to make it configurable via -$GIT_DIR/config mechanism. - -With this workflow, you would want to know: - -(1) ... if a topic branch has ever been merged to "next". Young - topic branches can have stupid mistakes you would rather - clean up before publishing, and things that have not been - merged into other branches can be easily rebased without - affecting other people. But once it is published, you would - not want to rewind it. - -(2) ... if a topic branch has been fully merged to "master". - Then you can delete it. More importantly, you should not - build on top of it -- other people may already want to - change things related to the topic as patches against your - "master", so if you need further changes, it is better to - fork the topic (perhaps with the same name) afresh from the - tip of "master". - -Let's look at this example: - - o---o---o---o---o---o---o---o---o---o "next" - / / / / - / a---a---b A / / - / / / / - / / c---c---c---c B / - / / / \ / - / / / b---b C \ / - / / / / \ / - ---o---o---o---o---o---o---o---o---o---o---o "master" - - -A, B and C are topic branches. - - * A has one fix since it was merged up to "next". - - * B has finished. It has been fully merged up to "master" and "next", - and is ready to be deleted. - - * C has not merged to "next" at all. - -We would want to allow C to be rebased, refuse A, and encourage -B to be deleted. - -To compute (1): - - git rev-list ^master ^topic next - git rev-list ^master next - - if these match, topic has not merged in next at all. - -To compute (2): - - git rev-list master..topic - - if this is empty, it is fully merged to "master". - -DOC_END diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-receive.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-receive.sample deleted file mode 100755 index a1fd29e..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/pre-receive.sample +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -# -# An example hook script to make use of push options. -# The example simply echoes all push options that start with 'echoback=' -# and rejects all pushes when the "reject" push option is used. -# -# To enable this hook, rename this file to "pre-receive". - -if test -n "$GIT_PUSH_OPTION_COUNT" -then - i=0 - while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" - do - eval "value=\$GIT_PUSH_OPTION_$i" - case "$value" in - echoback=*) - echo "echo from the pre-receive-hook: ${value#*=}" >&2 - ;; - reject) - exit 1 - esac - i=$((i + 1)) - done -fi diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/prepare-commit-msg.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/prepare-commit-msg.sample deleted file mode 100755 index 10fa14c..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/prepare-commit-msg.sample +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare the commit log message. -# Called by "git commit" with the name of the file that has the -# commit message, followed by the description of the commit -# message's source. The hook's purpose is to edit the commit -# message file. If the hook fails with a non-zero status, -# the commit is aborted. -# -# To enable this hook, rename this file to "prepare-commit-msg". - -# This hook includes three examples. The first one removes the -# "# Please enter the commit message..." help message. -# -# The second includes the output of "git diff --name-status -r" -# into the message, just before the "git status" output. It is -# commented because it doesn't cope with --amend or with squashed -# commits. -# -# The third example adds a Signed-off-by line to the message, that can -# still be edited. This is rarely a good idea. - -COMMIT_MSG_FILE=$1 -COMMIT_SOURCE=$2 -SHA1=$3 - -/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" - -# case "$COMMIT_SOURCE,$SHA1" in -# ,|template,) -# /usr/bin/perl -i.bak -pe ' -# print "\n" . `git diff --cached --name-status -r` -# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; -# *) ;; -# esac - -# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" -# if test -z "$COMMIT_SOURCE" -# then -# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" -# fi diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/push-to-checkout.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/push-to-checkout.sample deleted file mode 100755 index af5a0c0..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/push-to-checkout.sample +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/sh - -# An example hook script to update a checked-out tree on a git push. -# -# This hook is invoked by git-receive-pack(1) when it reacts to git -# push and updates reference(s) in its repository, and when the push -# tries to update the branch that is currently checked out and the -# receive.denyCurrentBranch configuration variable is set to -# updateInstead. -# -# By default, such a push is refused if the working tree and the index -# of the remote repository has any difference from the currently -# checked out commit; when both the working tree and the index match -# the current commit, they are updated to match the newly pushed tip -# of the branch. This hook is to be used to override the default -# behaviour; however the code below reimplements the default behaviour -# as a starting point for convenient modification. -# -# The hook receives the commit with which the tip of the current -# branch is going to be updated: -commit=$1 - -# It can exit with a non-zero status to refuse the push (when it does -# so, it must not modify the index or the working tree). -die () { - echo >&2 "$*" - exit 1 -} - -# Or it can make any necessary changes to the working tree and to the -# index to bring them to the desired state when the tip of the current -# branch is updated to the new commit, and exit with a zero status. -# -# For example, the hook can simply run git read-tree -u -m HEAD "$1" -# in order to emulate git fetch that is run in the reverse direction -# with git push, as the two-tree form of git read-tree -u -m is -# essentially the same as git switch or git checkout that switches -# branches while keeping the local changes in the working tree that do -# not interfere with the difference between the branches. - -# The below is a more-or-less exact translation to shell of the C code -# for the default behaviour for git's push-to-checkout hook defined in -# the push_to_deploy() function in builtin/receive-pack.c. -# -# Note that the hook will be executed from the repository directory, -# not from the working tree, so if you want to perform operations on -# the working tree, you will have to adapt your code accordingly, e.g. -# by adding "cd .." or using relative paths. - -if ! git update-index -q --ignore-submodules --refresh -then - die "Up-to-date check failed" -fi - -if ! git diff-files --quiet --ignore-submodules -- -then - die "Working directory has unstaged changes" -fi - -# This is a rough translation of: -# -# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX -if git cat-file -e HEAD 2>/dev/null -then - head=HEAD -else - head=$(git hash-object -t tree --stdin &2 - exit 1 -} - -unset GIT_DIR GIT_WORK_TREE -cd "$worktree" && - -if grep -q "^diff --git " "$1" -then - validate_patch "$1" -else - validate_cover_letter "$1" -fi && - -if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL" -then - git config --unset-all sendemail.validateWorktree && - trap 'git worktree remove -ff "$worktree"' EXIT && - validate_series -fi diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/update.sample b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/update.sample deleted file mode 100755 index c4d426b..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/hooks/update.sample +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/sh -# -# An example hook script to block unannotated tags from entering. -# Called by "git receive-pack" with arguments: refname sha1-old sha1-new -# -# To enable this hook, rename this file to "update". -# -# Config -# ------ -# hooks.allowunannotated -# This boolean sets whether unannotated tags will be allowed into the -# repository. By default they won't be. -# hooks.allowdeletetag -# This boolean sets whether deleting tags will be allowed in the -# repository. By default they won't be. -# hooks.allowmodifytag -# This boolean sets whether a tag may be modified after creation. By default -# it won't be. -# hooks.allowdeletebranch -# This boolean sets whether deleting branches will be allowed in the -# repository. By default they won't be. -# hooks.denycreatebranch -# This boolean sets whether remotely creating branches will be denied -# in the repository. By default this is allowed. -# - -# --- Command line -refname="$1" -oldrev="$2" -newrev="$3" - -# --- Safety check -if [ -z "$GIT_DIR" ]; then - echo "Don't run this script from the command line." >&2 - echo " (if you want, you could supply GIT_DIR then run" >&2 - echo " $0 )" >&2 - exit 1 -fi - -if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then - echo "usage: $0 " >&2 - exit 1 -fi - -# --- Config -allowunannotated=$(git config --type=bool hooks.allowunannotated) -allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) -denycreatebranch=$(git config --type=bool hooks.denycreatebranch) -allowdeletetag=$(git config --type=bool hooks.allowdeletetag) -allowmodifytag=$(git config --type=bool hooks.allowmodifytag) - -# check for no description -projectdesc=$(sed -e '1q' "$GIT_DIR/description") -case "$projectdesc" in -"Unnamed repository"* | "") - echo "*** Project description file hasn't been set" >&2 - exit 1 - ;; -esac - -# --- Check types -# if $newrev is 0000...0000, it's a commit to delete a ref. -zero=$(git hash-object --stdin &2 - echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 - exit 1 - fi - ;; - refs/tags/*,delete) - # delete tag - if [ "$allowdeletetag" != "true" ]; then - echo "*** Deleting a tag is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/tags/*,tag) - # annotated tag - if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 - then - echo "*** Tag '$refname' already exists." >&2 - echo "*** Modifying a tag is not allowed in this repository." >&2 - exit 1 - fi - ;; - refs/heads/*,commit) - # branch - if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then - echo "*** Creating a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/heads/*,delete) - # delete branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/remotes/*,commit) - # tracking branch - ;; - refs/remotes/*,delete) - # delete tracking branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a tracking branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - *) - # Anything else (is there anything else?) - echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 - exit 1 - ;; -esac - -# --- Finished -exit 0 diff --git a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/info/exclude b/sdk/IdeaSDK/.git.disabled-empty-nested-repo/info/exclude deleted file mode 100644 index a5196d1..0000000 --- a/sdk/IdeaSDK/.git.disabled-empty-nested-repo/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/sdk/IdeaSDK/.gitignore b/sdk/IdeaSDK/.gitignore deleted file mode 100644 index 374ce94..0000000 --- a/sdk/IdeaSDK/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -node_modules/ -dist/ -examples/hello-plugin/dist/ -examples/hello-plugin/build/ -*.tsbuildinfo -.DS_Store -coverage/ -npm-debug.log* -*.tgz diff --git a/sdk/IdeaSDK/README.md b/sdk/IdeaSDK/README.md deleted file mode 100644 index 40276e8..0000000 --- a/sdk/IdeaSDK/README.md +++ /dev/null @@ -1,383 +0,0 @@ -# IdeA Plugin SDK - -Minimal public TypeScript SDK for IdeA plugins. - -This first version intentionally stays small: - -- public manifest types for `idea-plugin.json`; -- public runtime types for plugin modules exposing `activate(ctx)`; -- a stable `ctx.services` facade for workspace, background task and terminal operations; -- public workspace file APIs for reading, writing, listing, stat and path resolution; -- a bounded generic project-structure query API; -- public command-task APIs for launching and tracking generic tools; -- public external-toolchain diagnostics for executables, env vars and files; -- public best-effort event subscriptions and workspace watch; -- public structured config-document helpers for JSON documents; -- a lightweight manifest validator; -- a minimal `examples/hello-plugin` plugin. - -## Install - -```sh -npm install -``` - -## Build - -```sh -npm run build -``` - -## Typecheck the example - -```sh -npm run typecheck:examples -``` - -## Build the installable hello plugin archive - -```sh -npm run package:hello-plugin -``` - -The archive is written to: - -```text -examples/hello-plugin/build/hello-plugin-0.1.0.zip -``` - -Its ZIP root contains `idea-plugin.json` directly, with no wrapping parent directory. The -compiled ESM entrypoint is emitted at `dist/index.js`, matching the manifest `main` field. - -## Plugin shape - -An IdeA plugin ships an `idea-plugin.json` manifest and a JavaScript entrypoint built from -TypeScript. - -```json -{ - "ideaPluginManifestVersion": 1, - "id": "com.example.hello", - "displayName": "Hello Plugin", - "version": "0.1.0", - "main": "dist/index.js", - "trustLevel": "full", - "contributes": {} -} -``` - -The entrypoint exports an `activate(ctx)` function: - -```ts -import type { ActivateContext } from "@idea/plugin-sdk"; - -export function activate(ctx: ActivateContext): void { - ctx.logger.info("hello from plugin"); -} -``` - -## Layout Runtime - -Plugins can contribute custom layout panels by declaring `contributes.layouts` -in `idea-plugin.json` and registering the matching layout type during -`activate(ctx)`. - -```json -{ - "contributes": { - "layouts": [ - { - "type": "com.example.status", - "label": "Status", - "component": "StatusPanel" - } - ] - } -} -``` - -```ts -import type { ActivateContext, PluginLayoutProps } from "@idea/plugin-sdk"; - -function StatusPanel(props: PluginLayoutProps): string { - return `status for ${props.projectId}`; -} - -export function activate(ctx: ActivateContext): void { - const disposable = ctx.layouts?.register({ - type: "com.example.status", - component: StatusPanel - }); - if (disposable) ctx.subscriptions.push(disposable); -} -``` - -Public layout props are: - -- `projectId`: project hosting the layout cell; -- `nodeId`: stable layout node id for that cell instance; -- `layoutType`: contributed layout type from the manifest; -- `state`: opaque JSON-serializable state persisted by the host; -- `setState(next)`: replaces that state; -- `availability`: currently `"available"` when the component is mounted. - -Lifecycle: register layouts during `activate(ctx)`, keep the returned disposable -in `ctx.subscriptions`, and let the host dispose it on plugin unload. Layout -components may be mounted, unmounted and remounted by the host; keep durable UI -state in `state` via `setState`, not in module globals. Call `setState` from -user actions, effects or asynchronous callbacks, not unconditionally while -rendering. Services are available from `ctx.services` to plugins declaring the -`tooling` capability; layout props do not expose private runtime gateways. - -## Runtime Services - -Plugins declaring the `tooling` capability receive `ctx.services`. Plugins -without that capability do not receive this facade. Prefer `ctx.services` over -IdeA's internal runtime objects when it is available: - -```ts -import type { ActivateContext } from "@idea/plugin-sdk"; - -export async function activate(ctx: ActivateContext): Promise { - const project = await ctx.services?.workspace.getCurrentProject(); - ctx.logger.info("current project", project); - - const task = await ctx.services?.tasks.getStatus("task-id"); - ctx.logger.info("task status", task?.status); - - const terminal = await ctx.services?.terminal.open({ rows: 24, cols: 80 }); - await terminal?.write(new TextEncoder().encode("echo hello\\r")); -} -``` - -### Workspace Files - -Workspace paths are always relative to the project root. Hosts reject absolute -paths, `..`, empty path segments and paths outside the sandbox. Text APIs use -UTF-8; binary APIs use `Uint8Array`. Missing files reject on reads and resolve -to `{ exists: false }` from `stat`. - -```ts -import type { ActivateContext } from "@idea/plugin-sdk"; - -export async function activate(ctx: ActivateContext): Promise { - const workspace = ctx.services?.workspace; - const project = await workspace?.getCurrentProject(); - if (!workspace || !project) return; - - await workspace.writeTextFile(".ideai/hello-plugin.txt", "hello\n", project.id); - - const file = await workspace.readTextFile(".ideai/hello-plugin.txt", project.id); - const listing = await workspace.listDirectory(".ideai", project.id); - const stat = await workspace.stat(file.path, project.id); - - ctx.logger.info("workspace file", { - path: file.path, - bytes: stat.len, - entries: listing.entries.length - }); -} -``` - -`watch(path, handler, projectId?)` subscribes to public workspace file-change -events for the given relative path. It is best-effort and bounded: plugins should -handle missed events by refreshing their own derived state when needed. - -### Project Structure - -`queryStructure()` returns a bounded, generic read model so plugins do not each -need to rescan the whole workspace for common markers: - -```ts -const structure = await ctx.services?.workspace.queryStructure({ - maxDepth: 3, - maxEntries: 500 -}); - -for (const convention of structure?.conventions ?? []) { - console.log(convention.id, convention.markerPath); -} -``` - -The MVP detects generic marker-file conventions such as `package.json`, -`Cargo.toml`, `pyproject.toml`, `go.mod`, `Makefile` and `.git`. It deliberately -does not expose language-specific ASTs or Android-specific concepts. - -Current terminal scope is intentionally minimal: it opens or reattaches a shell -PTY, writes bytes, resizes, detaches and closes. - -### Command Tasks - -Use `ctx.services.tasks.runCommand()` for non-interactive tools that should be -tracked as IdeA background tasks instead of opening a raw PTY. `command` and -`args` are passed separately, `cwd` is relative to the project root, and `env` -adds process environment variables. The current host requires an `ownerAgentId` -so the task can appear in Work and completion can be correlated to an agent. - -```ts -import type { ActivateContext } from "@idea/plugin-sdk"; - -export async function activate(ctx: ActivateContext): Promise { - const project = await ctx.services?.workspace.getCurrentProject(); - if (!project) return; - - const task = await ctx.services?.tasks.runCommand({ - projectId: project.id, - ownerAgentId: "00000000-0000-0000-0000-000000000000", - label: "Check npm", - command: "npm", - args: ["--version"], - cwd: ".", - env: { CI: "1" }, - recordOnly: true - }); - - const status = await ctx.services?.tasks.getCommandStatus(task.taskId); - ctx.logger.info("command task", { - taskId: task.taskId, - state: status?.state, - exitCode: status?.exitCode - }); -} -``` - -`list`, `getStatus`, `attachOutput`, `cancel` and `retry` continue to operate on -tasks visible through IdeA's Work read model. `getCommandStatus` reads a launched -command task directly from the host task store. - -### Toolchain Diagnostics - -Use `ctx.services.tooling.diagnose()` to check external prerequisites without -hard-coding one stack into the SDK. A request can probe executables, inspect -environment variables and validate workspace files in one structured result. - -```ts -import type { ActivateContext } from "@idea/plugin-sdk"; - -export async function activate(ctx: ActivateContext): Promise { - const diagnostic = await ctx.services?.tooling.diagnose({ - tools: [ - { - id: "node", - executable: "node", - versionArgs: ["--version"], - required: true - } - ], - env: [{ name: "PATH", required: true }], - files: [{ path: "package.json", kind: "file" }] - }); - - const node = diagnostic?.tools.find((tool) => tool.id === "node"); - ctx.logger.info("tooling diagnostic", { - ok: diagnostic?.ok, - nodePresent: node?.present, - nodeVersion: node?.version, - messages: diagnostic?.messages - }); -} -``` - -The diagnostic API is intentionally generic: it does not install tools, does not -model Android devices or emulators, and does not expose language-specific ASTs. - -### Events And Watch - -Use `ctx.services.events.subscribe()` for stable public host/project events. The -runtime hides the host polling details and returns a disposable subscription. - -```ts -import type { ActivateContext } from "@idea/plugin-sdk"; - -export async function activate(ctx: ActivateContext): Promise { - const subscription = await ctx.services?.events.subscribe( - { - eventTypes: ["backgroundTaskChanged"], - capacity: 100, - onDropped: (count) => ctx.logger.warn("plugin events dropped", { count }) - }, - (event) => { - if (event.type === "backgroundTaskChanged") { - ctx.logger.info("task changed", { - taskId: event.taskId, - state: event.state - }); - } - } - ); - - if (subscription) ctx.subscriptions.push(subscription); - - const watch = await ctx.services?.workspace.watch("src", (event) => { - ctx.logger.info("workspace changed", { - path: event.path, - kind: event.kind, - operation: event.operation - }); - }); - - if (watch) ctx.subscriptions.push(watch); -} -``` - -Public event retention is `bestEffortBounded`: events are retained per -subscription up to the requested/host-capped capacity, drained oldest-first, and -`onDropped` reports when older retained events were overwritten. - -### Structured Config Documents - -Use `ctx.services.config` when a plugin needs to read or update a structured -configuration file without reimplementing parsing and serialization. - -First-lot format support is deliberately narrow: - -- `json` only; -- inferred from `.json` when `format` is omitted; -- serialized as pretty JSON with a trailing newline; -- update modes: `mergePatch` and `replace`; -- `mergePatch` follows JSON merge-patch semantics: object keys are merged - recursively and `null` removes a key. - -```ts -import type { ActivateContext } from "@idea/plugin-sdk"; - -export async function activate(ctx: ActivateContext): Promise { - const config = await ctx.services?.config.readDocument({ - path: ".ideai/hello-plugin.json" - }); - - await ctx.services?.config.updateDocument({ - path: ".ideai/hello-plugin.json", - mode: "mergePatch", - value: { - enabled: true, - lastReadFormat: config?.format ?? "json" - } - }); -} -``` - -YAML, TOML, XML, `.properties` and stack-specific config models are not part of -this first lot. - -Declare the additive `tooling` capability to receive `ctx.services` at runtime: - -```json -{ - "capabilities": ["ui", "tooling"] -} -``` - -## Manifest Validation - -```ts -import { validatePluginManifest } from "@idea/plugin-sdk"; - -const result = validatePluginManifest(manifestJson); -if (!result.success) { - console.error(result.errors); -} -``` - -This validator is deliberately strict for core fields and permissive about future unknown fields. -It is not a security boundary. diff --git a/sdk/IdeaSDK/examples/hello-plugin/README.md b/sdk/IdeaSDK/examples/hello-plugin/README.md deleted file mode 100644 index 0b53e1a..0000000 --- a/sdk/IdeaSDK/examples/hello-plugin/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Hello Plugin - -Installable IdeA plugin example rebuilt from the public SDK types. - -It exercises the current plugin primitives end to end: - -- top-level menu: `Hello Plugin`; -- menu entry: `hello-plugin`; -- command: `hello-plugin`, returning `hello-world`; -- layout contribution: `hello-plugin.hello-world`, rendered as `hello-world`. -- tooling capability: logs the focused workspace project when `ctx.services` is available. - -```sh -npm run typecheck:examples -npm run package:hello-plugin -``` - -The installable archive is emitted at `examples/hello-plugin/build/hello-plugin-0.1.0.zip`. -It contains `idea-plugin.json` at the ZIP root and the compiled ESM entrypoint at -`dist/index.js`, matching the manifest `main` field. - -## Diagnostics - -During activation the plugin logs: - -- whether the command and layout runtime registries are available; -- successful registration of the `hello-plugin` command; -- successful registration of the `hello-plugin.hello-world` layout; -- availability of the workspace service from the `tooling` runtime capability; -- the first layout render, including project/node identifiers. - -These messages are intentionally small and stable so installation, bundle import, activation and -layout rendering failures can be separated quickly in IdeA logs/devtools. diff --git a/sdk/IdeaSDK/examples/hello-plugin/idea-plugin.json b/sdk/IdeaSDK/examples/hello-plugin/idea-plugin.json deleted file mode 100644 index cfc7dd7..0000000 --- a/sdk/IdeaSDK/examples/hello-plugin/idea-plugin.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "ideaPluginManifestVersion": 1, - "id": "com.example.hello-plugin", - "displayName": "Hello Plugin", - "publisher": "IdeA Examples", - "version": "0.1.0", - "description": "SDK example plugin for validating command, menu and layout loading.", - "main": "dist/index.js", - "engines": { - "idea": ">=0.1.0" - }, - "trustLevel": "full", - "capabilities": [ - "ui", - "tooling" - ], - "contributes": { - "menus": [ - { - "id": "hello-plugin.menu", - "label": "Hello Plugin", - "topLevel": true, - "order": 100 - } - ], - "menuItems": [ - { - "id": "hello-plugin.command.item", - "targetMenuId": "hello-plugin.menu", - "label": "hello-plugin", - "command": "hello-plugin", - "order": 10 - } - ], - "layouts": [ - { - "type": "hello-plugin.hello-world", - "label": "hello-world", - "component": "hello-world", - "order": 10 - } - ] - } -} diff --git a/sdk/IdeaSDK/examples/hello-plugin/package-lock.json b/sdk/IdeaSDK/examples/hello-plugin/package-lock.json deleted file mode 100644 index 9935509..0000000 --- a/sdk/IdeaSDK/examples/hello-plugin/package-lock.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "hello-plugin", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "hello-plugin", - "version": "0.1.0", - "dependencies": { - "@idea/plugin-sdk": "file:../.." - } - }, - "../..": { - "name": "@idea/plugin-sdk", - "version": "0.1.0", - "license": "MIT", - "devDependencies": { - "typescript": "^5.5.0" - } - }, - "node_modules/@idea/plugin-sdk": { - "resolved": "../..", - "link": true - } - } -} diff --git a/sdk/IdeaSDK/examples/hello-plugin/package.json b/sdk/IdeaSDK/examples/hello-plugin/package.json deleted file mode 100644 index 937f370..0000000 --- a/sdk/IdeaSDK/examples/hello-plugin/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "hello-plugin", - "version": "0.1.0", - "private": true, - "type": "module", - "main": "dist/index.js", - "scripts": { - "typecheck": "tsc -p tsconfig.json --noEmit" - }, - "dependencies": { - "@idea/plugin-sdk": "file:../.." - } -} - diff --git a/sdk/IdeaSDK/examples/hello-plugin/src/index.ts b/sdk/IdeaSDK/examples/hello-plugin/src/index.ts deleted file mode 100644 index e9ab78f..0000000 --- a/sdk/IdeaSDK/examples/hello-plugin/src/index.ts +++ /dev/null @@ -1,186 +0,0 @@ -import type { ActivateContext, IdeAPluginModule, PluginLayoutProps } from "@idea/plugin-sdk"; - -const COMMAND_ID = "hello-plugin"; -const LAYOUT_TYPE = "hello-plugin.hello-world"; - -let hasLoggedFirstLayoutRender = false; - -function HelloWorldLayout(props: PluginLayoutProps): string { - if (!hasLoggedFirstLayoutRender) { - hasLoggedFirstLayoutRender = true; - console.info("[hello-plugin] layout first render", { - projectId: props.projectId, - nodeId: props.nodeId, - layoutType: props.layoutType, - hasState: props.state !== undefined - }); - } - - return "hello-world"; -} - -export function activate(ctx: ActivateContext): void { - ctx.logger.info("activating hello-plugin", { - pluginId: ctx.pluginId, - hasCommands: Boolean(ctx.commands), - hasLayouts: Boolean(ctx.layouts) - }); - - const commandDisposable = ctx.commands?.registerCommand(COMMAND_ID, () => { - ctx.logger.info("command executed", { commandId: COMMAND_ID }); - return "hello-world"; - }); - - if (commandDisposable) { - ctx.subscriptions.push(commandDisposable); - ctx.logger.info("command registered", { commandId: COMMAND_ID }); - } else { - ctx.logger.warn("command registry unavailable", { commandId: COMMAND_ID }); - } - - const layoutDisposable = ctx.layouts?.register({ - type: LAYOUT_TYPE, - component: HelloWorldLayout - }); - - if (layoutDisposable) { - ctx.subscriptions.push(layoutDisposable); - ctx.logger.info("layout registered", { - layoutType: LAYOUT_TYPE, - component: "hello-world" - }); - } else { - ctx.logger.warn("layout registry unavailable", { layoutType: LAYOUT_TYPE }); - } - - void useWorkspaceSdk(ctx); -} - -async function useWorkspaceSdk(ctx: ActivateContext): Promise { - const workspace = ctx.services?.workspace; - if (!workspace) return; - - const project = await workspace.getCurrentProject(); - if (!project) { - ctx.logger.info("workspace service available without a focused project"); - return; - } - - const fixturePath = ".ideai/hello-plugin.txt"; - await workspace.writeTextFile(fixturePath, "hello from @idea/plugin-sdk\n", project.id); - const file = await workspace.readTextFile(fixturePath, project.id); - const stat = await workspace.stat(fixturePath, project.id); - const listing = await workspace.listDirectory(".ideai", project.id); - const structure = await workspace.queryStructure({ - projectId: project.id, - maxDepth: 2, - maxEntries: 100 - }); - - ctx.logger.info("workspace file round-trip complete", { - projectId: project.id, - path: file.path, - bytes: stat.len, - ideaiEntries: listing.entries.length, - conventions: structure.conventions.map((convention) => convention.id) - }); - - const diagnostic = await ctx.services?.tooling.diagnose({ - projectId: project.id, - tools: [ - { - id: "echo", - executable: "echo", - versionArgs: ["hello-plugin-toolcheck"], - required: true - } - ], - env: [{ name: "PATH", required: true }], - files: [{ path: fixturePath, kind: "file" }] - }); - - ctx.logger.info("tooling diagnostic complete", { - ok: diagnostic?.ok, - echoVersion: diagnostic?.tools.find((tool) => tool.id === "echo")?.version, - messages: diagnostic?.messages - }); - - const configPath = ".ideai/hello-plugin.json"; - await workspace.writeTextFile( - configPath, - JSON.stringify({ enabled: true, launches: 0 }, null, 2) + "\n", - project.id - ); - const configDocument = await ctx.services?.config.readDocument({ - projectId: project.id, - path: configPath - }); - await ctx.services?.config.updateDocument({ - projectId: project.id, - path: configPath, - mode: "mergePatch", - value: { lastFormat: configDocument?.format ?? "json", launches: 1 } - }); - ctx.logger.info("config document updated", { - path: configDocument?.path, - format: configDocument?.format - }); - - const watch = await workspace.watch(".ideai", (event) => { - ctx.logger.info("workspace watch event", { - path: event.path, - kind: event.kind, - operation: event.operation - }); - }, project.id); - ctx.subscriptions.push(watch); - - const events = await ctx.services?.events.subscribe( - { - projectId: project.id, - eventTypes: ["backgroundTaskChanged"], - pollIntervalMs: 2000, - onDropped: (count) => ctx.logger.warn("plugin events dropped", { count }) - }, - (event) => { - if (event.type === "backgroundTaskChanged") { - ctx.logger.info("background task changed", { - taskId: event.taskId, - state: event.state - }); - } - } - ); - if (events) ctx.subscriptions.push(events); - - const ownerAgentId = await ctx.storage?.get("helloPlugin.ownerAgentId"); - if (!ownerAgentId) { - ctx.logger.info("command task example skipped: no owner agent configured"); - return; - } - - const task = await ctx.services?.tasks.runCommand({ - projectId: project.id, - ownerAgentId, - label: "Hello plugin command", - command: "echo", - args: ["hello from @idea/plugin-sdk"], - cwd: ".", - recordOnly: true - }); - - if (task) { - const status = await ctx.services?.tasks.getCommandStatus(task.taskId); - ctx.logger.info("command task launched", { - taskId: task.taskId, - state: status?.state ?? task.state, - exitCode: status?.exitCode ?? task.exitCode - }); - } -} - -const plugin: IdeAPluginModule = { - activate -}; - -export default plugin; diff --git a/sdk/IdeaSDK/examples/hello-plugin/tsconfig.build.json b/sdk/IdeaSDK/examples/hello-plugin/tsconfig.build.json deleted file mode 100644 index 9121acc..0000000 --- a/sdk/IdeaSDK/examples/hello-plugin/tsconfig.build.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "declaration": false, - "declarationMap": false, - "noEmit": false, - "outDir": "dist", - "rootDir": "src", - "sourceMap": false, - "paths": { - "@idea/plugin-sdk": [ - "../../dist/index.d.ts" - ] - } - }, - "include": [ - "src/**/*.ts" - ] -} diff --git a/sdk/IdeaSDK/examples/hello-plugin/tsconfig.json b/sdk/IdeaSDK/examples/hello-plugin/tsconfig.json deleted file mode 100644 index 27c3a9e..0000000 --- a/sdk/IdeaSDK/examples/hello-plugin/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": false, - "declarationMap": false, - "noEmit": true, - "rootDir": "../..", - "paths": { - "@idea/plugin-sdk": [ - "../../src/index.ts" - ] - } - }, - "include": [ - "src/**/*.ts", - "../../src/**/*.ts" - ] -} diff --git a/sdk/IdeaSDK/package-lock.json b/sdk/IdeaSDK/package-lock.json deleted file mode 100644 index ffbe024..0000000 --- a/sdk/IdeaSDK/package-lock.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@idea/plugin-sdk", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@idea/plugin-sdk", - "version": "0.1.0", - "license": "MIT", - "devDependencies": { - "typescript": "^5.5.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - } - } -} diff --git a/sdk/IdeaSDK/package.json b/sdk/IdeaSDK/package.json deleted file mode 100644 index b6a16b1..0000000 --- a/sdk/IdeaSDK/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@idea/plugin-sdk", - "version": "0.1.0", - "description": "Minimal public TypeScript SDK for IdeA plugins.", - "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - } - }, - "files": [ - "dist", - "README.md" - ], - "scripts": { - "build": "tsc -p tsconfig.json", - "build:hello-plugin": "tsc -p examples/hello-plugin/tsconfig.build.json", - "package:hello-plugin": "npm run build && npm run build:hello-plugin && node scripts/package-hello-plugin.mjs", - "typecheck:examples": "tsc -p examples/hello-plugin/tsconfig.json --noEmit", - "check": "npm run build && npm run typecheck:examples && npm run package:hello-plugin" - }, - "keywords": [ - "idea", - "plugins", - "sdk" - ], - "license": "MIT", - "devDependencies": { - "typescript": "^5.5.0" - } -} diff --git a/sdk/IdeaSDK/scripts/package-hello-plugin.mjs b/sdk/IdeaSDK/scripts/package-hello-plugin.mjs deleted file mode 100644 index 3c8de90..0000000 --- a/sdk/IdeaSDK/scripts/package-hello-plugin.mjs +++ /dev/null @@ -1,124 +0,0 @@ -import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; -import { dirname, join } from "node:path"; - -const pluginRoot = join(process.cwd(), "examples", "hello-plugin"); -const manifestPath = join(pluginRoot, "idea-plugin.json"); -const manifest = JSON.parse(await readFile(manifestPath, "utf8")); - -const main = requireString(manifest, "main"); -const version = requireString(manifest, "version"); -const archivePath = join(pluginRoot, "build", `hello-plugin-${version}.zip`); -const archiveEntries = [ - { archivePath: "idea-plugin.json", sourcePath: manifestPath }, - { archivePath: main, sourcePath: join(pluginRoot, main) }, - { archivePath: "README.md", sourcePath: join(pluginRoot, "README.md") } -]; -const DOS_TIME_MIDNIGHT = 0; -const DOS_DATE_1980_01_01 = 33; -const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => { - let value = index; - - for (let bit = 0; bit < 8; bit += 1) { - value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1; - } - - return value >>> 0; -}); - -await rm(join(pluginRoot, "build"), { recursive: true, force: true }); -await mkdir(dirname(archivePath), { recursive: true }); - -const files = []; -for (const entry of archiveEntries) { - if (entry.archivePath.startsWith("/") || entry.archivePath.includes("..")) { - throw new Error(`Refusing unsafe archive path: ${entry.archivePath}`); - } - - files.push({ - archivePath: entry.archivePath, - data: await readFile(entry.sourcePath) - }); -} - -await writeFile(archivePath, createZip(files)); -console.log(`created ${archivePath}`); - -function requireString(record, key) { - if (typeof record[key] !== "string" || record[key].trim().length === 0) { - throw new Error(`idea-plugin.json field "${key}" must be a non-empty string`); - } - - return record[key]; -} - -function createZip(files) { - const localFileHeaders = []; - const centralDirectoryHeaders = []; - let offset = 0; - - for (const file of files) { - const filename = Buffer.from(file.archivePath, "utf8"); - const checksum = crc32(file.data); - const localFileHeader = Buffer.alloc(30); - - localFileHeader.writeUInt32LE(0x04034b50, 0); - localFileHeader.writeUInt16LE(20, 4); - localFileHeader.writeUInt16LE(0x0800, 6); - localFileHeader.writeUInt16LE(0, 8); - localFileHeader.writeUInt16LE(DOS_TIME_MIDNIGHT, 10); - localFileHeader.writeUInt16LE(DOS_DATE_1980_01_01, 12); - localFileHeader.writeUInt32LE(checksum, 14); - localFileHeader.writeUInt32LE(file.data.length, 18); - localFileHeader.writeUInt32LE(file.data.length, 22); - localFileHeader.writeUInt16LE(filename.length, 26); - localFileHeader.writeUInt16LE(0, 28); - - localFileHeaders.push(localFileHeader, filename, file.data); - - const centralDirectoryHeader = Buffer.alloc(46); - centralDirectoryHeader.writeUInt32LE(0x02014b50, 0); - centralDirectoryHeader.writeUInt16LE(20, 4); - centralDirectoryHeader.writeUInt16LE(20, 6); - centralDirectoryHeader.writeUInt16LE(0x0800, 8); - centralDirectoryHeader.writeUInt16LE(0, 10); - centralDirectoryHeader.writeUInt16LE(DOS_TIME_MIDNIGHT, 12); - centralDirectoryHeader.writeUInt16LE(DOS_DATE_1980_01_01, 14); - centralDirectoryHeader.writeUInt32LE(checksum, 16); - centralDirectoryHeader.writeUInt32LE(file.data.length, 20); - centralDirectoryHeader.writeUInt32LE(file.data.length, 24); - centralDirectoryHeader.writeUInt16LE(filename.length, 28); - centralDirectoryHeader.writeUInt16LE(0, 30); - centralDirectoryHeader.writeUInt16LE(0, 32); - centralDirectoryHeader.writeUInt16LE(0, 34); - centralDirectoryHeader.writeUInt16LE(0, 36); - centralDirectoryHeader.writeUInt32LE(0, 38); - centralDirectoryHeader.writeUInt32LE(offset, 42); - - centralDirectoryHeaders.push(centralDirectoryHeader, filename); - offset += localFileHeader.length + filename.length + file.data.length; - } - - const centralDirectory = Buffer.concat(centralDirectoryHeaders); - const endOfCentralDirectory = Buffer.alloc(22); - - endOfCentralDirectory.writeUInt32LE(0x06054b50, 0); - endOfCentralDirectory.writeUInt16LE(0, 4); - endOfCentralDirectory.writeUInt16LE(0, 6); - endOfCentralDirectory.writeUInt16LE(files.length, 8); - endOfCentralDirectory.writeUInt16LE(files.length, 10); - endOfCentralDirectory.writeUInt32LE(centralDirectory.length, 12); - endOfCentralDirectory.writeUInt32LE(offset, 16); - endOfCentralDirectory.writeUInt16LE(0, 20); - - return Buffer.concat([...localFileHeaders, centralDirectory, endOfCentralDirectory]); -} - -function crc32(data) { - let value = 0xffffffff; - - for (const byte of data) { - value = (value >>> 8) ^ CRC32_TABLE[(value ^ byte) & 0xff]; - } - - return (value ^ 0xffffffff) >>> 0; -} diff --git a/sdk/IdeaSDK/src/index.js b/sdk/IdeaSDK/src/index.js deleted file mode 100644 index 450bf53..0000000 --- a/sdk/IdeaSDK/src/index.js +++ /dev/null @@ -1 +0,0 @@ -export { isPluginManifest, assertPluginManifest, validatePluginManifest } from "./manifest.js"; diff --git a/sdk/IdeaSDK/src/index.ts b/sdk/IdeaSDK/src/index.ts deleted file mode 100644 index 3defaa2..0000000 --- a/sdk/IdeaSDK/src/index.ts +++ /dev/null @@ -1,85 +0,0 @@ -export type { - IdeAPluginCapability, - IdeAPluginManifest, - IdeAPluginEngineConstraints, - IdeAPluginLayoutContribution, - IdeAPluginMcpServerContribution, - IdeAPluginMenuItemContribution, - IdeAPluginTopLevelMenuContribution -} from "./manifest.js"; -export { - isPluginManifest, - assertPluginManifest, - validatePluginManifest -} from "./manifest.js"; -export type { - ActivateContext, - BackgroundTaskChangedEvent, - CommandDisposable, - CommandHandler, - CommandRegistry, - CommandTaskStatus, - ConfigDocument, - ConfigDocumentFormat, - ConfigDocumentReadOptions, - ConfigDocumentService, - ConfigDocumentUpdateOptions, - ConfigDocumentWriteResult, - ConfigUpdateMode, - DiagnosticMessage, - EnvDiagnostic, - EnvRequirement, - EventHandler, - EventService, - EventSubscribeOptions, - EventSubscription, - FileDiagnostic, - FileRequirement, - BackgroundTaskOutputAttachment, - BackgroundTaskRetryResult, - BackgroundTaskService, - BackgroundTaskStatus, - IdeAPluginModule, - JsonValue, - LayoutRegistry, - PluginLogger, - PluginLayoutAvailability, - PluginLayoutComponent, - PluginLayoutDefinition, - PluginLayoutProps, - PluginLayoutRenderResult, - PluginLayoutState, - PluginServices, - PluginStorage, - ProjectConvention, - ProjectModule, - ProjectStructure, - ProjectStructureEntry, - ProjectStructureEntryKind, - PublicEvent, - PublicEventType, - RunCommandTaskOptions, - TerminalOpenOptions, - TerminalReattachOptions, - TerminalReattachResult, - TerminalService, - TerminalSession, - ToolchainDiagnostic, - ToolchainDiagnosticRequest, - ToolDiagnostic, - ToolingService, - ToolRequirement, - WorkspaceBinaryFile, - WorkspaceDirEntry, - WorkspaceDirectoryListing, - WorkspaceFileChangedEvent, - WorkspaceProject, - WorkspaceResolvedPath, - WorkspaceService, - WorkspaceStat, - WorkspaceStructureQuery, - WorkspaceTextFile, - WorkspaceWatch, - WorkspaceWatchEvent, - WorkspaceWatchHandler -} from "./runtime.js"; diff --git a/sdk/IdeaSDK/src/manifest.js b/sdk/IdeaSDK/src/manifest.js deleted file mode 100644 index fc8cd50..0000000 --- a/sdk/IdeaSDK/src/manifest.js +++ /dev/null @@ -1,172 +0,0 @@ -const PLUGIN_ID_PATTERN = /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/; -const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/; -export function validatePluginManifest(input) { - const errors = []; - if (!isRecord(input)) { - return { success: false, errors: ["manifest must be an object"] }; - } - if (input.ideaPluginManifestVersion !== 1) { - errors.push("ideaPluginManifestVersion must be 1"); - } - requireString(input, "id", errors); - requireString(input, "displayName", errors); - requireString(input, "version", errors); - requireString(input, "main", errors); - if (input.trustLevel !== "full") { - errors.push("trustLevel must be full"); - } - optionalString(input, "description", errors); - optionalString(input, "publisher", errors); - if (typeof input.id === "string" && !PLUGIN_ID_PATTERN.test(input.id)) { - errors.push("id must contain lowercase letters, digits, dots or dashes, and start/end with an alphanumeric character"); - } - if (typeof input.version === "string" && !SEMVER_PATTERN.test(input.version)) { - errors.push("version must use semver syntax, for example 0.1.0"); - } - validateEngines(input.engines, errors); - validateCapabilities(input.capabilities, errors); - validateContributes(input.contributes, errors); - if (errors.length > 0) { - return { success: false, errors }; - } - return { success: true, data: input, errors: [] }; -} -export function isPluginManifest(input) { - return validatePluginManifest(input).success; -} -export function assertPluginManifest(input) { - const result = validatePluginManifest(input); - if (!result.success) { - throw new Error(`Invalid IdeA plugin manifest: ${result.errors.join("; ")}`); - } -} -function validateEngines(value, errors) { - if (value === undefined) { - return; - } - if (!isRecord(value)) { - errors.push("engines must be an object when provided"); - return; - } - optionalString(value, "idea", errors, "engines.idea"); -} -function validateCapabilities(value, errors) { - if (value === undefined) { - return; - } - if (!Array.isArray(value)) { - errors.push("capabilities must be an array when provided"); - return; - } - value.forEach((capability, index) => { - if (capability !== "ui" && capability !== "mcp" && capability !== "tooling") { - errors.push(`capabilities[${index}] must be "ui", "mcp" or "tooling"`); - } - }); -} -function validateContributes(value, errors) { - if (value === undefined) { - return; - } - if (!isRecord(value)) { - errors.push("contributes must be an object when provided"); - return; - } - validateArray(value, "menus", errors, (menu, index) => { - requireString(menu, "id", errors, `contributes.menus[${index}].id`); - requireString(menu, "label", errors, `contributes.menus[${index}].label`); - if (menu.topLevel !== true) { - errors.push(`contributes.menus[${index}].topLevel must be true`); - } - optionalNumber(menu, "order", errors, `contributes.menus[${index}].order`); - optionalString(menu, "icon", errors, `contributes.menus[${index}].icon`); - }); - validateArray(value, "menuItems", errors, (item, index) => { - requireString(item, "id", errors, `contributes.menuItems[${index}].id`); - requireString(item, "targetMenuId", errors, `contributes.menuItems[${index}].targetMenuId`); - requireString(item, "label", errors, `contributes.menuItems[${index}].label`); - requireString(item, "command", errors, `contributes.menuItems[${index}].command`); - optionalNumber(item, "order", errors, `contributes.menuItems[${index}].order`); - optionalString(item, "icon", errors, `contributes.menuItems[${index}].icon`); - optionalString(item, "when", errors, `contributes.menuItems[${index}].when`); - }); - validateArray(value, "layouts", errors, (layout, index) => { - requireString(layout, "type", errors, `contributes.layouts[${index}].type`); - requireString(layout, "label", errors, `contributes.layouts[${index}].label`); - requireString(layout, "component", errors, `contributes.layouts[${index}].component`); - optionalNumber(layout, "order", errors, `contributes.layouts[${index}].order`); - optionalString(layout, "icon", errors, `contributes.layouts[${index}].icon`); - optionalString(layout, "when", errors, `contributes.layouts[${index}].when`); - }); - validateArray(value, "mcpServers", errors, (server, index) => { - requireString(server, "id", errors, `contributes.mcpServers[${index}].id`); - requireString(server, "displayName", errors, `contributes.mcpServers[${index}].displayName`); - requireString(server, "command", errors, `contributes.mcpServers[${index}].command`); - if (server.transport !== "stdio") { - errors.push(`contributes.mcpServers[${index}].transport must be "stdio"`); - } - optionalStringArray(server, "args", errors, `contributes.mcpServers[${index}].args`); - optionalStringRecord(server, "env", errors, `contributes.mcpServers[${index}].env`); - optionalString(server, "cwd", errors, `contributes.mcpServers[${index}].cwd`); - optionalBoolean(server, "autoStart", errors, `contributes.mcpServers[${index}].autoStart`); - optionalBoolean(server, "allowAbsoluteCommand", errors, `contributes.mcpServers[${index}].allowAbsoluteCommand`); - }); -} -function validateArray(record, key, errors, validateItem) { - const value = record[key]; - if (value === undefined) { - return; - } - if (!Array.isArray(value)) { - errors.push(`contributes.${key} must be an array when provided`); - return; - } - value.forEach((item, index) => { - if (!isRecord(item)) { - errors.push(`contributes.${key}[${index}] must be an object`); - return; - } - validateItem(item, index); - }); -} -function requireString(record, key, errors, label = key) { - if (typeof record[key] !== "string" || record[key].trim().length === 0) { - errors.push(`${label} must be a non-empty string`); - } -} -function optionalString(record, key, errors, label = key) { - if (record[key] !== undefined && typeof record[key] !== "string") { - errors.push(`${label} must be a string when provided`); - } -} -function optionalNumber(record, key, errors, label = key) { - if (record[key] !== undefined && typeof record[key] !== "number") { - errors.push(`${label} must be a number when provided`); - } -} -function optionalBoolean(record, key, errors, label = key) { - if (record[key] !== undefined && typeof record[key] !== "boolean") { - errors.push(`${label} must be a boolean when provided`); - } -} -function optionalStringArray(record, key, errors, label = key) { - const value = record[key]; - if (value === undefined) { - return; - } - if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) { - errors.push(`${label} must be an array of strings when provided`); - } -} -function optionalStringRecord(record, key, errors, label = key) { - const value = record[key]; - if (value === undefined) { - return; - } - if (!isRecord(value) || Object.values(value).some((item) => typeof item !== "string")) { - errors.push(`${label} must be an object of strings when provided`); - } -} -function isRecord(value) { - return typeof value === "object" && value !== null && !Array.isArray(value); -} diff --git a/sdk/IdeaSDK/src/manifest.ts b/sdk/IdeaSDK/src/manifest.ts deleted file mode 100644 index 3b33509..0000000 --- a/sdk/IdeaSDK/src/manifest.ts +++ /dev/null @@ -1,309 +0,0 @@ -export interface IdeAPluginManifest { - ideaPluginManifestVersion: 1; - id: string; - displayName: string; - version: string; - main: string; - trustLevel: "full"; - description?: string; - publisher?: string; - engines?: IdeAPluginEngineConstraints; - capabilities?: IdeAPluginCapability[]; - contributes?: { - menus?: IdeAPluginTopLevelMenuContribution[]; - menuItems?: IdeAPluginMenuItemContribution[]; - layouts?: IdeAPluginLayoutContribution[]; - mcpServers?: IdeAPluginMcpServerContribution[]; - }; -} - -export type IdeAPluginCapability = "ui" | "mcp" | "tooling"; - -export interface IdeAPluginEngineConstraints { - idea?: string; -} - -export interface IdeAPluginTopLevelMenuContribution { - id: string; - label: string; - topLevel: true; - order?: number; - icon?: string; -} - -export interface IdeAPluginMenuItemContribution { - id: string; - targetMenuId: string; - label: string; - command: string; - order?: number; - icon?: string; - when?: string; -} - -export interface IdeAPluginLayoutContribution { - type: string; - label: string; - component: string; - order?: number; - icon?: string; - when?: string; -} - -export interface IdeAPluginMcpServerContribution { - id: string; - displayName: string; - command: string; - args?: string[]; - env?: Record; - cwd?: string; - transport: "stdio"; - autoStart?: boolean; - allowAbsoluteCommand?: boolean; -} - -export type PluginManifestValidationResult = - | { success: true; data: IdeAPluginManifest; errors: [] } - | { success: false; data?: undefined; errors: string[] }; - -const PLUGIN_ID_PATTERN = /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/; -const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/; - -export function validatePluginManifest(input: unknown): PluginManifestValidationResult { - const errors: string[] = []; - - if (!isRecord(input)) { - return { success: false, errors: ["manifest must be an object"] }; - } - - if (input.ideaPluginManifestVersion !== 1) { - errors.push("ideaPluginManifestVersion must be 1"); - } - - requireString(input, "id", errors); - requireString(input, "displayName", errors); - requireString(input, "version", errors); - requireString(input, "main", errors); - if (input.trustLevel !== "full") { - errors.push("trustLevel must be full"); - } - optionalString(input, "description", errors); - optionalString(input, "publisher", errors); - - if (typeof input.id === "string" && !PLUGIN_ID_PATTERN.test(input.id)) { - errors.push("id must contain lowercase letters, digits, dots or dashes, and start/end with an alphanumeric character"); - } - - if (typeof input.version === "string" && !SEMVER_PATTERN.test(input.version)) { - errors.push("version must use semver syntax, for example 0.1.0"); - } - - validateEngines(input.engines, errors); - validateCapabilities(input.capabilities, errors); - validateContributes(input.contributes, errors); - - if (errors.length > 0) { - return { success: false, errors }; - } - - return { success: true, data: input as unknown as IdeAPluginManifest, errors: [] }; -} - -export function isPluginManifest(input: unknown): input is IdeAPluginManifest { - return validatePluginManifest(input).success; -} - -export function assertPluginManifest(input: unknown): asserts input is IdeAPluginManifest { - const result = validatePluginManifest(input); - - if (!result.success) { - throw new Error(`Invalid IdeA plugin manifest: ${result.errors.join("; ")}`); - } -} - -function validateEngines(value: unknown, errors: string[]): void { - if (value === undefined) { - return; - } - - if (!isRecord(value)) { - errors.push("engines must be an object when provided"); - return; - } - - optionalString(value, "idea", errors, "engines.idea"); -} - -function validateCapabilities(value: unknown, errors: string[]): void { - if (value === undefined) { - return; - } - - if (!Array.isArray(value)) { - errors.push("capabilities must be an array when provided"); - return; - } - - value.forEach((capability, index) => { - if (capability !== "ui" && capability !== "mcp" && capability !== "tooling") { - errors.push(`capabilities[${index}] must be "ui", "mcp" or "tooling"`); - } - }); -} - -function validateContributes(value: unknown, errors: string[]): void { - if (value === undefined) { - return; - } - - if (!isRecord(value)) { - errors.push("contributes must be an object when provided"); - return; - } - - validateArray(value, "menus", errors, (menu, index) => { - requireString(menu, "id", errors, `contributes.menus[${index}].id`); - requireString(menu, "label", errors, `contributes.menus[${index}].label`); - if (menu.topLevel !== true) { - errors.push(`contributes.menus[${index}].topLevel must be true`); - } - optionalNumber(menu, "order", errors, `contributes.menus[${index}].order`); - optionalString(menu, "icon", errors, `contributes.menus[${index}].icon`); - }); - validateArray(value, "menuItems", errors, (item, index) => { - requireString(item, "id", errors, `contributes.menuItems[${index}].id`); - requireString(item, "targetMenuId", errors, `contributes.menuItems[${index}].targetMenuId`); - requireString(item, "label", errors, `contributes.menuItems[${index}].label`); - requireString(item, "command", errors, `contributes.menuItems[${index}].command`); - optionalNumber(item, "order", errors, `contributes.menuItems[${index}].order`); - optionalString(item, "icon", errors, `contributes.menuItems[${index}].icon`); - optionalString(item, "when", errors, `contributes.menuItems[${index}].when`); - }); - validateArray(value, "layouts", errors, (layout, index) => { - requireString(layout, "type", errors, `contributes.layouts[${index}].type`); - requireString(layout, "label", errors, `contributes.layouts[${index}].label`); - requireString(layout, "component", errors, `contributes.layouts[${index}].component`); - optionalNumber(layout, "order", errors, `contributes.layouts[${index}].order`); - optionalString(layout, "icon", errors, `contributes.layouts[${index}].icon`); - optionalString(layout, "when", errors, `contributes.layouts[${index}].when`); - }); - validateArray(value, "mcpServers", errors, (server, index) => { - requireString(server, "id", errors, `contributes.mcpServers[${index}].id`); - requireString(server, "displayName", errors, `contributes.mcpServers[${index}].displayName`); - requireString(server, "command", errors, `contributes.mcpServers[${index}].command`); - if (server.transport !== "stdio") { - errors.push(`contributes.mcpServers[${index}].transport must be "stdio"`); - } - optionalStringArray(server, "args", errors, `contributes.mcpServers[${index}].args`); - optionalStringRecord(server, "env", errors, `contributes.mcpServers[${index}].env`); - optionalString(server, "cwd", errors, `contributes.mcpServers[${index}].cwd`); - optionalBoolean(server, "autoStart", errors, `contributes.mcpServers[${index}].autoStart`); - optionalBoolean(server, "allowAbsoluteCommand", errors, `contributes.mcpServers[${index}].allowAbsoluteCommand`); - }); -} - -function validateArray( - record: Record, - key: string, - errors: string[], - validateItem: (item: Record, index: number) => void -): void { - const value = record[key]; - if (value === undefined) { - return; - } - - if (!Array.isArray(value)) { - errors.push(`contributes.${key} must be an array when provided`); - return; - } - - value.forEach((item, index) => { - if (!isRecord(item)) { - errors.push(`contributes.${key}[${index}] must be an object`); - return; - } - - validateItem(item, index); - }); -} - -function requireString( - record: Record, - key: string, - errors: string[], - label = key -): void { - if (typeof record[key] !== "string" || record[key].trim().length === 0) { - errors.push(`${label} must be a non-empty string`); - } -} - -function optionalString( - record: Record, - key: string, - errors: string[], - label = key -): void { - if (record[key] !== undefined && typeof record[key] !== "string") { - errors.push(`${label} must be a string when provided`); - } -} - -function optionalNumber( - record: Record, - key: string, - errors: string[], - label = key -): void { - if (record[key] !== undefined && typeof record[key] !== "number") { - errors.push(`${label} must be a number when provided`); - } -} - -function optionalBoolean( - record: Record, - key: string, - errors: string[], - label = key -): void { - if (record[key] !== undefined && typeof record[key] !== "boolean") { - errors.push(`${label} must be a boolean when provided`); - } -} - -function optionalStringArray( - record: Record, - key: string, - errors: string[], - label = key -): void { - const value = record[key]; - if (value === undefined) { - return; - } - - if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) { - errors.push(`${label} must be an array of strings when provided`); - } -} - -function optionalStringRecord( - record: Record, - key: string, - errors: string[], - label = key -): void { - const value = record[key]; - if (value === undefined) { - return; - } - - if (!isRecord(value) || Object.values(value).some((item) => typeof item !== "string")) { - errors.push(`${label} must be an object of strings when provided`); - } -} - -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} diff --git a/sdk/IdeaSDK/src/runtime.js b/sdk/IdeaSDK/src/runtime.js deleted file mode 100644 index cb0ff5c..0000000 --- a/sdk/IdeaSDK/src/runtime.js +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/sdk/IdeaSDK/src/runtime.ts b/sdk/IdeaSDK/src/runtime.ts deleted file mode 100644 index f5edfa3..0000000 --- a/sdk/IdeaSDK/src/runtime.ts +++ /dev/null @@ -1,532 +0,0 @@ -export interface ActivateContext { - pluginId: string; - logger: PluginLogger; - subscriptions: CommandDisposable[]; - commands?: CommandRegistry; - layouts?: LayoutRegistry; - storage?: PluginStorage; - /** - * Stable public service facade for plugins that need workspace, background - * task, or terminal operations. This intentionally does not expose IdeA's - * internal runtime/gateway objects. - */ - services?: PluginServices; -} - -export interface IdeAPluginModule { - activate(ctx: ActivateContext): void | Promise; - deactivate?(): void | Promise; -} - -export interface PluginLogger { - debug(message: string, ...args: unknown[]): void; - info(message: string, ...args: unknown[]): void; - warn(message: string, ...args: unknown[]): void; - error(message: string, ...args: unknown[]): void; -} - -export type CommandHandler = (...args: unknown[]) => unknown | Promise; - -export interface CommandRegistry { - registerCommand(commandId: string, handler: CommandHandler): CommandDisposable; -} - -export interface CommandDisposable { - dispose(): void; -} - -export interface PluginStorage { - get(key: string): Promise; - set(key: string, value: T): Promise; - delete(key: string): Promise; -} - -export type PluginLayoutState = JsonValue | undefined; -export type PluginLayoutAvailability = "available"; -export type PluginLayoutRenderResult = unknown; - -export interface PluginLayoutProps { - /** Project currently hosting this layout cell. */ - projectId: string; - /** Stable layout node id for this cell instance. */ - nodeId: string; - /** Layout contribution type declared in `idea-plugin.json`. */ - layoutType: string; - /** Opaque JSON-serializable state persisted by the host for this cell. */ - state: TState; - /** Replaces the opaque state for this cell. Values must be JSON-serializable. */ - setState(next: TState): void; - /** Present layouts are only mounted when available; fallback UI is host-owned. */ - availability: PluginLayoutAvailability; -} - -export type PluginLayoutComponent = ( - props: PluginLayoutProps, -) => PluginLayoutRenderResult; - -export interface PluginLayoutDefinition { - /** Must match a layout `type` declared in this plugin's manifest. */ - type: string; - component: PluginLayoutComponent; -} - -export interface LayoutRegistry { - register( - definition: PluginLayoutDefinition, - ): CommandDisposable; -} - -export interface PluginServices { - workspace: WorkspaceService; - tasks: BackgroundTaskService; - tooling: ToolingService; - events: EventService; - config: ConfigDocumentService; - terminal: TerminalService; -} - -export interface WorkspaceProject { - id: string; - name: string; - root: string; -} - -export interface WorkspaceService { - /** Returns the currently focused project, or null when no project is active. */ - getCurrentProject(): Promise; - /** Returns the root path for the given project or for the current project. */ - getProjectRoot(projectId?: string): Promise; - /** Reads IdeA's shared project context for the given or current project. */ - readProjectContext(projectId?: string): Promise; - /** Updates IdeA's shared project context for the given or current project. */ - updateProjectContext(content: string, projectId?: string): Promise; - /** - * Resolves and normalizes a plugin-visible path under the project root. - * Rejects absolute paths, `..`, empty segments and other paths the host - * considers outside the workspace sandbox. - */ - resolvePath(path: string, projectId?: string): Promise; - /** Reads a UTF-8 text file under the project root. */ - readTextFile(path: string, projectId?: string): Promise; - /** Reads raw bytes from a file under the project root. */ - readBinaryFile(path: string, projectId?: string): Promise; - /** Writes UTF-8 text under the project root using the host's controlled write path. */ - writeTextFile(path: string, content: string, projectId?: string): Promise; - /** Writes raw bytes under the project root using the host's controlled write path. */ - writeBinaryFile(path: string, bytes: Uint8Array, projectId?: string): Promise; - /** Lists one directory under the project root. Defaults to the workspace root. */ - listDirectory(path?: string, projectId?: string): Promise; - /** - * Returns basic metadata. Missing paths resolve to `{ exists: false }`; invalid - * paths and permission errors reject. - */ - stat(path: string, projectId?: string): Promise; - /** - * Extension point for host file watching. The MVP SDK reserves the public - * shape; hosts may reject with a clear not-implemented error until #127 lands. - */ - watch(path: string, handler: WorkspaceWatchHandler, projectId?: string): Promise; - /** Queries a bounded, generic project structure read model. */ - queryStructure(query?: WorkspaceStructureQuery): Promise; -} - -export interface WorkspaceResolvedPath { - projectId: string; - root: string; - path: string; -} - -export interface WorkspaceTextFile { - path: string; - content: string; -} - -export interface WorkspaceBinaryFile { - path: string; - bytes: Uint8Array; -} - -export interface WorkspaceDirEntry { - name: string; - path: string; - isDir: boolean; -} - -export interface WorkspaceDirectoryListing { - path: string; - entries: WorkspaceDirEntry[]; -} - -export interface WorkspaceStat { - path: string; - exists: boolean; - isFile: boolean; - isDir: boolean; - len: number | null; -} - -export interface WorkspaceWatchEvent { - path: string; - kind: "created" | "modified" | "deleted" | "renamed" | "unknown"; - operation: string; - projectId: string; -} - -export type WorkspaceWatchHandler = (event: WorkspaceWatchEvent) => void; - -export interface WorkspaceWatch { - dispose(): void; -} - -export interface WorkspaceStructureQuery { - projectId?: string; - path?: string; - maxDepth?: number; - maxEntries?: number; -} - -export type ProjectStructureEntryKind = "file" | "directory"; - -export interface ProjectStructureEntry { - path: string; - name: string; - kind: ProjectStructureEntryKind; -} - -export interface ProjectConvention { - id: string; - markerPath: string; -} - -export interface ProjectModule { - path: string; - markerPath: string; - conventionId: string; -} - -export interface ProjectStructure { - projectId: string; - rootPath: string; - entries: ProjectStructureEntry[]; - conventions: ProjectConvention[]; - modules: ProjectModule[]; - truncated: boolean; -} - -export interface BackgroundTaskStatus { - taskId: string; - ownerAgentId: string; - projectId: string; - kind: string; - status: "pending" | "running" | "completed" | "failed" | "cancelled" | "delivered"; - exitCode: number | null; - summary: string | null; - stdoutTail: string | null; - stderrTail: string | null; - updatedAtMs: number; -} - -export interface BackgroundTaskOutputAttachment { - taskId: string; - scrollback: Uint8Array; - live: boolean; - detach(): void; -} - -export interface BackgroundTaskRetryResult { - /** Present when the host reports the replacement task id. */ - taskId?: string; -} - -export interface RunCommandTaskOptions { - /** Project that owns the command workspace. Defaults to the focused project. */ - projectId?: string; - /** Agent id used by IdeA Work for ownership, cancellation and completion delivery. */ - ownerAgentId: string; - /** Human-facing label shown in Work. Defaults to the command line. */ - label?: string; - /** Executable to run. Arguments are passed separately, without shell parsing. */ - command: string; - /** Arguments passed to the executable. */ - args?: string[]; - /** Relative working directory under the project root. Defaults to the root. */ - cwd?: string; - /** Extra environment variables for the command. */ - env?: Record | Array<[string, string]>; - /** When true, completion is recorded without waking the owner agent. */ - recordOnly?: boolean; - /** Optional absolute deadline, epoch milliseconds. */ - deadlineMs?: number; -} - -export interface CommandTaskStatus { - taskId: string; - ownerAgentId: string; - projectId: string; - kind: string; - state: "queued" | "running" | "waiting" | "completed" | "failed" | "cancelled" | "expired"; - exitCode: number | null; - summary: string | null; - stdoutTail: string | null; - stderrTail: string | null; - createdAtMs: number; - updatedAtMs: number; -} - -export interface ToolRequirement { - /** Stable id chosen by the plugin for this executable prerequisite. */ - id: string; - /** Executable name or path to probe. */ - executable: string; - /** Version/diagnostic arguments. Defaults host-side to `--version`. */ - versionArgs?: string[]; - /** Whether this tool must pass for the whole diagnostic to be ok. */ - required?: boolean; - /** Extra environment variables for this probe. */ - env?: Record | Array<[string, string]>; -} - -export interface EnvRequirement { - /** Environment variable name. */ - name: string; - /** Whether the variable must be present and match. */ - required?: boolean; - /** Optional exact expected value. */ - equals?: string; -} - -export interface FileRequirement { - /** Relative workspace path. */ - path: string; - /** Whether the path must exist and match `kind`. */ - required?: boolean; - /** Expected workspace path kind. */ - kind?: "file" | "directory" | "any"; -} - -export interface ToolchainDiagnosticRequest { - /** Project to inspect. Defaults to the focused project. */ - projectId?: string; - /** Relative working directory under the project root. Defaults to the root. */ - cwd?: string; - /** Executable probes to run. */ - tools?: ToolRequirement[]; - /** Environment variable prerequisites to inspect. */ - env?: EnvRequirement[]; - /** Workspace file prerequisites to validate. */ - files?: FileRequirement[]; -} - -export interface ToolchainDiagnostic { - projectId: string; - cwd: string; - ok: boolean; - tools: ToolDiagnostic[]; - env: EnvDiagnostic[]; - files: FileDiagnostic[]; - messages: DiagnosticMessage[]; -} - -export interface ToolDiagnostic { - id: string; - executable: string; - present: boolean; - ok: boolean; - status: "ok" | "failed" | "missing"; - required: boolean; - exitCode: number | null; - version: string | null; - stdout: string | null; - stderr: string | null; - error: string | null; -} - -export interface EnvDiagnostic { - name: string; - present: boolean; - ok: boolean; - required: boolean; - value: string | null; - status: "ok" | "missing" | "mismatch"; -} - -export interface FileDiagnostic { - path: string; - exists: boolean; - ok: boolean; - required: boolean; - kind: "file" | "directory" | "other" | "missing"; - expectedKind: "file" | "directory" | "any" | null; - len: number | null; -} - -export interface DiagnosticMessage { - level: "info" | "warning" | "error"; - message: string; -} - -export interface ToolingService { - /** Runs generic external-toolchain diagnostics for executables, env and files. */ - diagnose(request: ToolchainDiagnosticRequest): Promise; -} - -export type PublicEventType = "workspaceFileChanged" | "backgroundTaskChanged"; - -export type PublicEvent = WorkspaceFileChangedEvent | BackgroundTaskChangedEvent; - -export interface WorkspaceFileChangedEvent { - type: "workspaceFileChanged"; - sequence: number; - occurredAtMs: number; - projectId: string; - path: string; - operation: string; -} - -export interface BackgroundTaskChangedEvent { - type: "backgroundTaskChanged"; - sequence: number; - occurredAtMs: number; - projectId: string; - taskId: string; - ownerAgentId: string; - state: string; -} - -export interface EventSubscribeOptions { - /** Project to observe. Defaults to the focused project. */ - projectId?: string; - /** Public event types to retain. Empty/omitted means every supported event. */ - eventTypes?: PublicEventType[]; - /** Per-subscription retained capacity. Host clamps to its supported bounds. */ - capacity?: number; - /** Polling cadence used by the runtime facade. Defaults to 1000 ms. */ - pollIntervalMs?: number; - /** Maximum events drained per poll. Host clamps to its supported bounds. */ - maxEventsPerPoll?: number; - /** Called when the host reports dropped retained events for this subscription. */ - onDropped?: (count: number) => void; -} - -export interface EventSubscription { - readonly subscriptionId: string; - readonly projectId: string; - readonly eventTypes: PublicEventType[]; - readonly retention: string; - dispose(): void; -} - -export type EventHandler = (event: PublicEvent) => void; - -export interface EventService { - /** Subscribes to stable, best-effort bounded public host/project events. */ - subscribe(options: EventSubscribeOptions, handler: EventHandler): Promise; -} - -export type JsonValue = - | null - | boolean - | number - | string - | JsonValue[] - | { [key: string]: JsonValue }; - -export type ConfigDocumentFormat = "json"; -export type ConfigUpdateMode = "mergePatch" | "replace"; - -export interface ConfigDocumentReadOptions { - /** Project that owns the config document. Defaults to the focused project. */ - projectId?: string; - /** Relative path under the project root. */ - path: string; - /** Explicit format. Omit to infer from extension. First lot supports only `json`. */ - format?: ConfigDocumentFormat; -} - -export interface ConfigDocumentUpdateOptions extends ConfigDocumentReadOptions { - /** Update mode. Defaults host-side to `mergePatch`. */ - mode?: ConfigUpdateMode; - /** Replacement value or JSON merge patch. */ - value: JsonValue; -} - -export interface ConfigDocument { - projectId: string; - path: string; - format: ConfigDocumentFormat; - value: T; -} - -export interface ConfigDocumentWriteResult { - projectId: string; - path: string; - format: ConfigDocumentFormat; - mode: ConfigUpdateMode; - bytesWritten: number; -} - -export interface ConfigDocumentService { - /** Reads and parses a structured config document. First lot supports JSON only. */ - readDocument( - options: ConfigDocumentReadOptions, - ): Promise>; - /** Writes a full replacement or JSON merge patch. First lot supports JSON only. */ - updateDocument(options: ConfigDocumentUpdateOptions): Promise; -} - -export interface BackgroundTaskService { - /** Launches a non-interactive command as a first-class IdeA background task. */ - runCommand(options: RunCommandTaskOptions): Promise; - /** Reads one command task directly from the host task store. */ - getCommandStatus(taskId: string): Promise; - /** Lists background tasks visible in the project work-state read model. */ - list(projectId?: string): Promise; - /** Reads one task status from the project work-state read model. */ - getStatus(taskId: string, projectId?: string): Promise; - /** Attaches to retained/live output for a task. */ - attachOutput( - taskId: string, - onData: (bytes: Uint8Array) => void, - ): Promise; - /** Cancels a pending/running task. */ - cancel(taskId: string): Promise; - /** Retries a failed/cancelled task; future hosts may return the new task id. */ - retry(taskId: string): Promise; -} - -export interface TerminalOpenOptions { - cwd?: string; - rows?: number; - cols?: number; - onData?: (bytes: Uint8Array) => void; -} - -export interface TerminalReattachOptions { - onData?: (bytes: Uint8Array) => void; -} - -export interface TerminalSession { - readonly sessionId: string; - write(data: Uint8Array): Promise; - resize(rows: number, cols: number): Promise; - detach(): void; - close(): Promise; -} - -export interface TerminalReattachResult { - session: TerminalSession; - scrollback: Uint8Array; -} - -export interface TerminalService { - /** - * Opens a shell PTY in the requested/current project directory. This MVP is a - * terminal control surface, not a command runner; use tasks for build/test - * commands that should be tracked in the Work panel. - */ - open(options?: TerminalOpenOptions): Promise; - /** Reattaches to an already-running PTY and returns retained scrollback. */ - reattach(sessionId: string, options?: TerminalReattachOptions): Promise; - /** Kills a PTY by id. */ - close(sessionId: string): Promise; -} diff --git a/sdk/IdeaSDK/tsconfig.json b/sdk/IdeaSDK/tsconfig.json deleted file mode 100644 index 136dafe..0000000 --- a/sdk/IdeaSDK/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "outDir": "dist", - "rootDir": "src", - "strict": true, - "skipLibCheck": true - }, - "include": [ - "src/**/*.ts" - ] -} -